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@50
|
89 Policy policy, QString ellipsis)
|
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@50
|
103 while (tw > maxWidth && truncated > 1) {
|
Chris@50
|
104
|
Chris@50
|
105 truncated--;
|
Chris@50
|
106
|
Chris@50
|
107 if (truncated > ellipsis.length()) {
|
Chris@50
|
108 text = abbreviateTo(original, truncated, policy, ellipsis);
|
Chris@50
|
109 } else {
|
Chris@50
|
110 break;
|
Chris@50
|
111 }
|
Chris@50
|
112
|
Chris@50
|
113 tw = metrics.width(text);
|
Chris@50
|
114 }
|
Chris@50
|
115
|
Chris@50
|
116 maxWidth = tw;
|
Chris@50
|
117 return text;
|
Chris@50
|
118 }
|
Chris@50
|
119
|
Chris@50
|
120 QStringList
|
Chris@50
|
121 TextAbbrev::abbreviate(const QStringList &texts, int maxLength,
|
Chris@50
|
122 Policy policy, bool fuzzy, QString ellipsis)
|
Chris@50
|
123 {
|
Chris@50
|
124 if (policy == ElideEndAndCommonPrefixes &&
|
Chris@50
|
125 texts.size() > 1) {
|
Chris@50
|
126
|
Chris@50
|
127 if (ellipsis == "") ellipsis = getDefaultEllipsis();
|
Chris@50
|
128 int fl = (fuzzy ? getFuzzLength(ellipsis) : 0);
|
Chris@50
|
129 if (maxLength <= ellipsis.length()) maxLength = ellipsis.length() + 1;
|
Chris@50
|
130
|
Chris@50
|
131 int maxOrigLength = 0;
|
Chris@50
|
132 for (int i = 0; i < texts.size(); ++i) {
|
Chris@50
|
133 int len = texts[i].length();
|
Chris@50
|
134 if (len > maxOrigLength) maxOrigLength = len;
|
Chris@50
|
135 }
|
Chris@50
|
136 if (maxOrigLength <= maxLength + fl) return texts;
|
Chris@50
|
137
|
Chris@50
|
138 return abbreviate(elidePrefixes
|
Chris@50
|
139 (texts, maxOrigLength - maxLength, ellipsis),
|
Chris@50
|
140 maxLength, ElideEnd, fuzzy, ellipsis);
|
Chris@50
|
141 }
|
Chris@50
|
142
|
Chris@50
|
143 QStringList results;
|
Chris@50
|
144 for (int i = 0; i < texts.size(); ++i) {
|
Chris@50
|
145 results.push_back
|
Chris@50
|
146 (abbreviate(texts[i], maxLength, policy, fuzzy, ellipsis));
|
Chris@50
|
147 }
|
Chris@50
|
148 return results;
|
Chris@50
|
149 }
|
Chris@50
|
150
|
Chris@50
|
151 QStringList
|
Chris@50
|
152 TextAbbrev::abbreviate(const QStringList &texts, const QFontMetrics &metrics,
|
Chris@50
|
153 int &maxWidth, Policy policy, QString ellipsis)
|
Chris@50
|
154 {
|
Chris@50
|
155 if (policy == ElideEndAndCommonPrefixes &&
|
Chris@50
|
156 texts.size() > 1) {
|
Chris@50
|
157
|
Chris@50
|
158 if (ellipsis == "") ellipsis = getDefaultEllipsis();
|
Chris@50
|
159
|
Chris@50
|
160 int maxOrigWidth = 0;
|
Chris@50
|
161 for (int i = 0; i < texts.size(); ++i) {
|
Chris@50
|
162 int w = metrics.width(texts[i]);
|
Chris@50
|
163 if (w > maxOrigWidth) maxOrigWidth = w;
|
Chris@50
|
164 }
|
Chris@50
|
165
|
Chris@50
|
166 return abbreviate(elidePrefixes(texts, metrics,
|
Chris@50
|
167 maxOrigWidth - maxWidth, ellipsis),
|
Chris@50
|
168 metrics, maxWidth, ElideEnd, ellipsis);
|
Chris@50
|
169 }
|
Chris@50
|
170
|
Chris@50
|
171 QStringList results;
|
Chris@50
|
172 int maxAbbrWidth = 0;
|
Chris@50
|
173 for (int i = 0; i < texts.size(); ++i) {
|
Chris@50
|
174 int width = maxWidth;
|
Chris@50
|
175 QString abbr = abbreviate(texts[i], metrics, width, policy, ellipsis);
|
Chris@50
|
176 if (width > maxAbbrWidth) maxAbbrWidth = width;
|
Chris@50
|
177 results.push_back(abbr);
|
Chris@50
|
178 }
|
Chris@50
|
179 maxWidth = maxAbbrWidth;
|
Chris@50
|
180 return results;
|
Chris@50
|
181 }
|
Chris@50
|
182
|
Chris@50
|
183 QStringList
|
Chris@50
|
184 TextAbbrev::elidePrefixes(const QStringList &texts,
|
Chris@50
|
185 int targetReduction,
|
Chris@50
|
186 QString ellipsis)
|
Chris@50
|
187 {
|
Chris@50
|
188 if (texts.empty()) return texts;
|
Chris@50
|
189 int plen = getPrefixLength(texts);
|
Chris@50
|
190 int fl = getFuzzLength(ellipsis);
|
Chris@50
|
191 if (plen < fl) return texts;
|
Chris@50
|
192
|
Chris@50
|
193 QString prefix = texts[0].left(plen);
|
Chris@50
|
194 int truncated = plen;
|
Chris@50
|
195 if (plen >= targetReduction + fl) {
|
Chris@50
|
196 truncated = plen - targetReduction;
|
Chris@50
|
197 } else {
|
Chris@50
|
198 truncated = fl;
|
Chris@50
|
199 }
|
Chris@50
|
200 prefix = abbreviate(prefix, truncated, ElideEnd, false, ellipsis);
|
Chris@50
|
201
|
Chris@50
|
202 QStringList results;
|
Chris@50
|
203 for (int i = 0; i < texts.size(); ++i) {
|
Chris@50
|
204 results.push_back
|
Chris@50
|
205 (prefix + texts[i].right(texts[i].length() - plen));
|
Chris@50
|
206 }
|
Chris@50
|
207 return results;
|
Chris@50
|
208 }
|
Chris@50
|
209
|
Chris@50
|
210 QStringList
|
Chris@50
|
211 TextAbbrev::elidePrefixes(const QStringList &texts,
|
Chris@50
|
212 const QFontMetrics &metrics,
|
Chris@50
|
213 int targetWidthReduction,
|
Chris@50
|
214 QString ellipsis)
|
Chris@50
|
215 {
|
Chris@50
|
216 if (texts.empty()) return texts;
|
Chris@50
|
217 int plen = getPrefixLength(texts);
|
Chris@50
|
218 int fl = getFuzzLength(ellipsis);
|
Chris@50
|
219 if (plen < fl) return texts;
|
Chris@50
|
220
|
Chris@50
|
221 QString prefix = texts[0].left(plen);
|
Chris@50
|
222 int pwid = metrics.width(prefix);
|
Chris@50
|
223 int twid = pwid - targetWidthReduction;
|
Chris@50
|
224 if (twid < metrics.width(ellipsis) * 2) twid = metrics.width(ellipsis) * 2;
|
Chris@50
|
225 prefix = abbreviate(prefix, metrics, twid, ElideEnd, ellipsis);
|
Chris@50
|
226
|
Chris@50
|
227 QStringList results;
|
Chris@50
|
228 for (int i = 0; i < texts.size(); ++i) {
|
Chris@50
|
229 results.push_back
|
Chris@50
|
230 (prefix + texts[i].right(texts[i].length() - plen));
|
Chris@50
|
231 }
|
Chris@50
|
232 return results;
|
Chris@50
|
233 }
|
Chris@50
|
234
|
Chris@50
|
235 static bool
|
Chris@50
|
236 havePrefix(QString prefix, const QStringList &texts)
|
Chris@50
|
237 {
|
Chris@50
|
238 for (int i = 1; i < texts.size(); ++i) {
|
Chris@50
|
239 if (!texts[i].startsWith(prefix)) return false;
|
Chris@50
|
240 }
|
Chris@50
|
241 return true;
|
Chris@50
|
242 }
|
Chris@50
|
243
|
Chris@50
|
244 int
|
Chris@50
|
245 TextAbbrev::getPrefixLength(const QStringList &texts)
|
Chris@50
|
246 {
|
Chris@50
|
247 QString reference = texts[0];
|
Chris@50
|
248
|
Chris@50
|
249 if (reference == "" || havePrefix(reference, texts)) {
|
Chris@50
|
250 return reference.length();
|
Chris@50
|
251 }
|
Chris@50
|
252
|
Chris@50
|
253 int candidate = reference.length();
|
Chris@50
|
254 QString splitChars(";:,./#-!()$_+=[]{}\\");
|
Chris@50
|
255
|
Chris@50
|
256 while (--candidate > 1) {
|
Chris@50
|
257 if (splitChars.contains(reference[candidate])) {
|
Chris@50
|
258 if (havePrefix(reference.left(candidate), texts)) {
|
Chris@50
|
259 break;
|
Chris@50
|
260 }
|
Chris@50
|
261 }
|
Chris@50
|
262 }
|
Chris@50
|
263
|
Chris@50
|
264 // std::cerr << "TextAbbrev::getPrefixLength: prefix length is " << candidate << std::endl;
|
Chris@50
|
265 // for (int i = 0; i < texts.size(); ++i) {
|
Chris@50
|
266 // std::cerr << texts[i].left(candidate).toStdString() << "|" << texts[i].right(texts[i].length() - candidate).toStdString() << std::endl;
|
Chris@50
|
267 // }
|
Chris@50
|
268
|
Chris@50
|
269 return candidate;
|
Chris@50
|
270 }
|
Chris@50
|
271
|