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