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