Chris@151
|
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
|
Chris@151
|
2
|
Chris@151
|
3 /*
|
Chris@151
|
4 Sonic Visualiser
|
Chris@151
|
5 An audio file viewer and annotation editor.
|
Chris@151
|
6 Centre for Digital Music, Queen Mary, University of London.
|
Chris@182
|
7 This file copyright 2006 QMUL.
|
Chris@151
|
8
|
Chris@151
|
9 This program is free software; you can redistribute it and/or
|
Chris@151
|
10 modify it under the terms of the GNU General Public License as
|
Chris@151
|
11 published by the Free Software Foundation; either version 2 of the
|
Chris@151
|
12 License, or (at your option) any later version. See the file
|
Chris@151
|
13 COPYING included with this distribution for more information.
|
Chris@151
|
14 */
|
Chris@151
|
15
|
Chris@151
|
16 #include "SubdividingMenu.h"
|
Chris@151
|
17
|
Chris@151
|
18 #include <iostream>
|
Chris@151
|
19
|
Chris@682
|
20 #include "base/Debug.h"
|
Chris@682
|
21
|
Chris@151
|
22 using std::set;
|
Chris@151
|
23 using std::map;
|
Chris@151
|
24
|
Chris@1347
|
25 //#define DEBUG_SUBDIVIDING_MENU 1
|
Chris@1347
|
26
|
Chris@807
|
27 SubdividingMenu::SubdividingMenu(int lowerLimit, int upperLimit,
|
Chris@152
|
28 QWidget *parent) :
|
Chris@152
|
29 QMenu(parent),
|
Chris@152
|
30 m_lowerLimit(lowerLimit ? lowerLimit : 14),
|
Chris@152
|
31 m_upperLimit(upperLimit ? upperLimit : (m_lowerLimit * 5) / 2),
|
Chris@152
|
32 m_entriesSet(false)
|
Chris@151
|
33 {
|
Chris@1347
|
34 #ifdef DEBUG_SUBDIVIDING_MENU
|
Chris@1347
|
35 cerr << "SubdividingMenu: constructed without title" << endl;
|
Chris@1347
|
36 #endif
|
Chris@151
|
37 }
|
Chris@151
|
38
|
Chris@807
|
39 SubdividingMenu::SubdividingMenu(const QString &title, int lowerLimit,
|
Chris@807
|
40 int upperLimit, QWidget *parent) :
|
Chris@152
|
41 QMenu(title, parent),
|
Chris@152
|
42 m_lowerLimit(lowerLimit ? lowerLimit : 14),
|
Chris@152
|
43 m_upperLimit(upperLimit ? upperLimit : (m_lowerLimit * 5) / 2),
|
Chris@152
|
44 m_entriesSet(false)
|
Chris@151
|
45 {
|
Chris@1347
|
46 #ifdef DEBUG_SUBDIVIDING_MENU
|
Chris@1347
|
47 cerr << "SubdividingMenu: constructed with title \""
|
Chris@1347
|
48 << title << "\"" << endl;
|
Chris@1347
|
49 #endif
|
Chris@151
|
50 }
|
Chris@151
|
51
|
Chris@151
|
52 SubdividingMenu::~SubdividingMenu()
|
Chris@151
|
53 {
|
Chris@152
|
54 for (map<QString, QObject *>::iterator i = m_pendingEntries.begin();
|
Chris@152
|
55 i != m_pendingEntries.end(); ++i) {
|
Chris@152
|
56 delete i->second;
|
Chris@152
|
57 }
|
Chris@151
|
58 }
|
Chris@151
|
59
|
Chris@151
|
60 void
|
Chris@151
|
61 SubdividingMenu::setEntries(const std::set<QString> &entries)
|
Chris@151
|
62 {
|
Chris@152
|
63 m_entriesSet = true;
|
Chris@152
|
64
|
Chris@1347
|
65 #ifdef DEBUG_SUBDIVIDING_MENU
|
Chris@1347
|
66 cerr << "SubdividingMenu::setEntries(" << title() << "): "
|
Chris@1347
|
67 << entries.size() << " entries" << endl;
|
Chris@1347
|
68 #endif
|
Chris@1347
|
69
|
Chris@908
|
70 int total = int(entries.size());
|
Chris@151
|
71
|
Chris@152
|
72 if (total < m_upperLimit) return;
|
Chris@151
|
73
|
Chris@807
|
74 int count = 0;
|
Chris@151
|
75 QMenu *chunkMenu = new QMenu();
|
Chris@196
|
76 chunkMenu->setTearOffEnabled(isTearOffEnabled());
|
Chris@151
|
77
|
Chris@151
|
78 QString firstNameInChunk;
|
Chris@151
|
79 QChar firstInitialInChunk;
|
Chris@151
|
80 bool discriminateStartInitial = false;
|
Chris@151
|
81
|
Chris@1347
|
82 // Re-sort using locale-aware comparator
|
Chris@151
|
83
|
Chris@1347
|
84 auto comparator = [](QString s1, QString s2) -> bool {
|
Chris@1347
|
85 return QString::localeAwareCompare(s1, s2) < 0;
|
Chris@1347
|
86 };
|
Chris@1347
|
87
|
Chris@1358
|
88 set<QString, decltype(comparator)> sortedEntries(comparator);
|
Chris@1347
|
89 sortedEntries.insert(entries.begin(), entries.end());
|
Chris@1347
|
90
|
Chris@1347
|
91 for (auto j = sortedEntries.begin(); j != sortedEntries.end(); ++j) {
|
Chris@1347
|
92
|
Chris@1347
|
93 #ifdef DEBUG_SUBDIVIDING_MENU
|
Chris@1347
|
94 cerr << "SubdividingMenu::setEntries: entry is: " << j->toStdString() << endl;
|
Chris@1347
|
95 #endif
|
Chris@151
|
96
|
Chris@151
|
97 m_nameToChunkMenuMap[*j] = chunkMenu;
|
Chris@151
|
98
|
Chris@1347
|
99 auto k = j;
|
Chris@151
|
100 ++k;
|
Chris@151
|
101
|
Chris@1347
|
102 QChar initial = (*j)[0].toUpper();
|
Chris@151
|
103
|
Chris@151
|
104 if (count == 0) {
|
Chris@151
|
105 firstNameInChunk = *j;
|
Chris@151
|
106 firstInitialInChunk = initial;
|
Chris@1347
|
107 #ifdef DEBUG_SUBDIVIDING_MENU
|
Chris@1347
|
108 cerr << "starting new chunk at initial " << initial << endl;
|
Chris@1347
|
109 #endif
|
Chris@151
|
110 }
|
Chris@151
|
111
|
Chris@1347
|
112 #ifdef DEBUG_SUBDIVIDING_MENU
|
Chris@1347
|
113 cerr << "count = "<< count << ", upper limit = " << m_upperLimit << endl;
|
Chris@1347
|
114 #endif
|
Chris@152
|
115
|
Chris@1347
|
116 bool lastInChunk = (k == sortedEntries.end() ||
|
Chris@152
|
117 (count >= m_lowerLimit-1 &&
|
Chris@152
|
118 (count == m_upperLimit ||
|
Chris@1347
|
119 (*k)[0].toUpper() != initial)));
|
Chris@151
|
120
|
Chris@151
|
121 ++count;
|
Chris@151
|
122
|
Chris@151
|
123 if (lastInChunk) {
|
Chris@151
|
124
|
Chris@1347
|
125 bool discriminateEndInitial = (k != sortedEntries.end() &&
|
Chris@1347
|
126 (*k)[0].toUpper() == initial);
|
Chris@151
|
127
|
Chris@151
|
128 bool initialsEqual = (firstInitialInChunk == initial);
|
Chris@151
|
129
|
Chris@151
|
130 QString from = QString("%1").arg(firstInitialInChunk);
|
Chris@151
|
131 if (discriminateStartInitial ||
|
Chris@151
|
132 (discriminateEndInitial && initialsEqual)) {
|
Chris@151
|
133 from = firstNameInChunk.left(3);
|
Chris@151
|
134 }
|
Chris@151
|
135
|
Chris@151
|
136 QString to = QString("%1").arg(initial);
|
Chris@151
|
137 if (discriminateEndInitial ||
|
Chris@151
|
138 (discriminateStartInitial && initialsEqual)) {
|
Chris@151
|
139 to = j->left(3);
|
Chris@151
|
140 }
|
Chris@151
|
141
|
Chris@151
|
142 QString menuText;
|
Chris@151
|
143
|
Chris@151
|
144 if (from == to) menuText = from;
|
Chris@151
|
145 else menuText = tr("%1 - %2").arg(from).arg(to);
|
Chris@151
|
146
|
Chris@151
|
147 discriminateStartInitial = discriminateEndInitial;
|
Chris@151
|
148
|
Chris@151
|
149 chunkMenu->setTitle(menuText);
|
Chris@151
|
150
|
Chris@151
|
151 QMenu::addMenu(chunkMenu);
|
Chris@151
|
152
|
Chris@151
|
153 chunkMenu = new QMenu();
|
Chris@196
|
154 chunkMenu->setTearOffEnabled(isTearOffEnabled());
|
Chris@151
|
155
|
Chris@151
|
156 count = 0;
|
Chris@151
|
157 }
|
Chris@151
|
158 }
|
Chris@151
|
159
|
Chris@151
|
160 if (count == 0) delete chunkMenu;
|
Chris@151
|
161 }
|
Chris@151
|
162
|
Chris@151
|
163 void
|
Chris@152
|
164 SubdividingMenu::entriesAdded()
|
Chris@152
|
165 {
|
Chris@152
|
166 if (m_entriesSet) {
|
Chris@1347
|
167 SVCERR << "ERROR: SubdividingMenu::entriesAdded: setEntries was also called -- should use one mechanism or the other, but not both" << endl;
|
Chris@152
|
168 return;
|
Chris@152
|
169 }
|
Chris@152
|
170
|
Chris@152
|
171 set<QString> entries;
|
Chris@1347
|
172 for (auto i: m_pendingEntries) {
|
Chris@1347
|
173 entries.insert(i.first);
|
Chris@1347
|
174 }
|
Chris@1347
|
175 setEntries(entries);
|
Chris@1347
|
176
|
Chris@1347
|
177 // Re-sort using locale-aware comparator (setEntries will do this
|
Chris@1347
|
178 // again, for the set passed to it, but we need the same sorting
|
Chris@1347
|
179 // for the subsequent loop in this function as well)
|
Chris@1347
|
180 auto comparator = [](QString s1, QString s2) -> bool {
|
Chris@1347
|
181 return QString::localeAwareCompare(s1, s2) < 0;
|
Chris@1347
|
182 };
|
Chris@1358
|
183 set<QString, decltype(comparator)> sortedEntries(comparator);
|
Chris@1347
|
184 for (auto i: m_pendingEntries) {
|
Chris@1347
|
185 sortedEntries.insert(i.first);
|
Chris@152
|
186 }
|
Chris@152
|
187
|
Chris@1347
|
188 for (QString entry: sortedEntries) {
|
Chris@152
|
189
|
Chris@1347
|
190 QObject *obj = m_pendingEntries[entry];
|
Chris@1347
|
191
|
Chris@1347
|
192 QMenu *menu = dynamic_cast<QMenu *>(obj);
|
Chris@152
|
193 if (menu) {
|
Chris@1347
|
194 addMenu(entry, menu);
|
Chris@152
|
195 continue;
|
Chris@152
|
196 }
|
Chris@152
|
197
|
Chris@1347
|
198 QAction *action = dynamic_cast<QAction *>(obj);
|
Chris@152
|
199 if (action) {
|
Chris@1347
|
200 addAction(entry, action);
|
Chris@152
|
201 continue;
|
Chris@152
|
202 }
|
Chris@152
|
203 }
|
Chris@152
|
204
|
Chris@152
|
205 m_pendingEntries.clear();
|
Chris@152
|
206 }
|
Chris@152
|
207
|
Chris@152
|
208 void
|
Chris@151
|
209 SubdividingMenu::addAction(QAction *action)
|
Chris@151
|
210 {
|
Chris@151
|
211 QString name = action->text();
|
Chris@151
|
212
|
Chris@152
|
213 if (!m_entriesSet) {
|
Chris@152
|
214 m_pendingEntries[name] = action;
|
Chris@152
|
215 return;
|
Chris@152
|
216 }
|
Chris@152
|
217
|
Chris@151
|
218 if (m_nameToChunkMenuMap.find(name) == m_nameToChunkMenuMap.end()) {
|
Chris@1347
|
219 #ifdef DEBUG_SUBDIVIDING_MENU
|
Chris@1347
|
220 cerr << "SubdividingMenu::addAction(" << title() << " | " << name << "): not found in name-to-chunk map, adding to main menu" << endl;
|
Chris@1347
|
221 #endif
|
Chris@151
|
222 QMenu::addAction(action);
|
Chris@151
|
223 return;
|
Chris@151
|
224 }
|
Chris@151
|
225
|
Chris@1347
|
226 #ifdef DEBUG_SUBDIVIDING_MENU
|
Chris@1347
|
227 cerr << "SubdividingMenu::addAction(" << title() << " | " << name << "): found in name-to-chunk map for menu " << m_nameToChunkMenuMap[name]->title() << endl;
|
Chris@1347
|
228 #endif
|
Chris@151
|
229 m_nameToChunkMenuMap[name]->addAction(action);
|
Chris@151
|
230 }
|
Chris@151
|
231
|
Chris@151
|
232 QAction *
|
Chris@151
|
233 SubdividingMenu::addAction(const QString &name)
|
Chris@151
|
234 {
|
Chris@152
|
235 if (!m_entriesSet) {
|
Chris@152
|
236 QAction *action = new QAction(name, this);
|
Chris@152
|
237 m_pendingEntries[name] = action;
|
Chris@152
|
238 return action;
|
Chris@152
|
239 }
|
Chris@152
|
240
|
Chris@151
|
241 if (m_nameToChunkMenuMap.find(name) == m_nameToChunkMenuMap.end()) {
|
Chris@1347
|
242 #ifdef DEBUG_SUBDIVIDING_MENU
|
Chris@1347
|
243 cerr << "SubdividingMenu::addAction(" << title() << " | " << name << "): not found in name-to-chunk map, adding to main menu" << endl;
|
Chris@1347
|
244 #endif
|
Chris@151
|
245 return QMenu::addAction(name);
|
Chris@151
|
246 }
|
Chris@151
|
247
|
Chris@1347
|
248 #ifdef DEBUG_SUBDIVIDING_MENU
|
Chris@1347
|
249 cerr << "SubdividingMenu::addAction(" << title() << " | " << name << "): found in name-to-chunk map for menu " << m_nameToChunkMenuMap[name]->title() << endl;
|
Chris@1347
|
250 #endif
|
Chris@151
|
251 return m_nameToChunkMenuMap[name]->addAction(name);
|
Chris@151
|
252 }
|
Chris@151
|
253
|
Chris@151
|
254 void
|
Chris@151
|
255 SubdividingMenu::addAction(const QString &name, QAction *action)
|
Chris@151
|
256 {
|
Chris@152
|
257 if (!m_entriesSet) {
|
Chris@152
|
258 m_pendingEntries[name] = action;
|
Chris@152
|
259 return;
|
Chris@152
|
260 }
|
Chris@152
|
261
|
Chris@151
|
262 if (m_nameToChunkMenuMap.find(name) == m_nameToChunkMenuMap.end()) {
|
Chris@1347
|
263 #ifdef DEBUG_SUBDIVIDING_MENU
|
Chris@1347
|
264 cerr << "SubdividingMenu::addAction(" << title() << " | " << name << "): not found in name-to-chunk map, adding to main menu" << endl;
|
Chris@1347
|
265 #endif
|
Chris@151
|
266 QMenu::addAction(action);
|
Chris@151
|
267 return;
|
Chris@151
|
268 }
|
Chris@151
|
269
|
Chris@1347
|
270 #ifdef DEBUG_SUBDIVIDING_MENU
|
Chris@1347
|
271 cerr << "SubdividingMenu::addAction(" << title() << " | " << name << "): found in name-to-chunk map for menu " << m_nameToChunkMenuMap[name]->title() << endl;
|
Chris@1347
|
272 #endif
|
Chris@151
|
273 m_nameToChunkMenuMap[name]->addAction(action);
|
Chris@151
|
274 }
|
Chris@151
|
275
|
Chris@151
|
276 void
|
Chris@151
|
277 SubdividingMenu::addMenu(QMenu *menu)
|
Chris@151
|
278 {
|
Chris@151
|
279 QString name = menu->title();
|
Chris@151
|
280
|
Chris@152
|
281 if (!m_entriesSet) {
|
Chris@152
|
282 m_pendingEntries[name] = menu;
|
Chris@152
|
283 return;
|
Chris@152
|
284 }
|
Chris@152
|
285
|
Chris@151
|
286 if (m_nameToChunkMenuMap.find(name) == m_nameToChunkMenuMap.end()) {
|
Chris@1347
|
287 #ifdef DEBUG_SUBDIVIDING_MENU
|
Chris@1347
|
288 cerr << "SubdividingMenu::addMenu(" << title() << " | " << name << "): not found in name-to-chunk map, adding to main menu" << endl;
|
Chris@1347
|
289 #endif
|
Chris@151
|
290 QMenu::addMenu(menu);
|
Chris@151
|
291 return;
|
Chris@151
|
292 }
|
Chris@151
|
293
|
Chris@1347
|
294 #ifdef DEBUG_SUBDIVIDING_MENU
|
Chris@1347
|
295 cerr << "SubdividingMenu::addMenu(" << title() << " | " << name << "): found in name-to-chunk map for menu " << m_nameToChunkMenuMap[name]->title() << endl;
|
Chris@1347
|
296 #endif
|
Chris@151
|
297 m_nameToChunkMenuMap[name]->addMenu(menu);
|
Chris@151
|
298 }
|
Chris@151
|
299
|
Chris@151
|
300 QMenu *
|
Chris@151
|
301 SubdividingMenu::addMenu(const QString &name)
|
Chris@151
|
302 {
|
Chris@152
|
303 if (!m_entriesSet) {
|
Chris@152
|
304 QMenu *menu = new QMenu(name, this);
|
Chris@196
|
305 menu->setTearOffEnabled(isTearOffEnabled());
|
Chris@152
|
306 m_pendingEntries[name] = menu;
|
Chris@152
|
307 return menu;
|
Chris@152
|
308 }
|
Chris@152
|
309
|
Chris@151
|
310 if (m_nameToChunkMenuMap.find(name) == m_nameToChunkMenuMap.end()) {
|
Chris@1347
|
311 #ifdef DEBUG_SUBDIVIDING_MENU
|
Chris@1347
|
312 cerr << "SubdividingMenu::addMenu(" << title() << " | " << name << "): not found in name-to-chunk map, adding to main menu" << endl;
|
Chris@1347
|
313 #endif
|
Chris@151
|
314 return QMenu::addMenu(name);
|
Chris@151
|
315 }
|
Chris@151
|
316
|
Chris@1347
|
317 #ifdef DEBUG_SUBDIVIDING_MENU
|
Chris@1347
|
318 cerr << "SubdividingMenu::addMenu(" << title() << " | " << name << "): found in name-to-chunk map for menu " << m_nameToChunkMenuMap[name]->title() << endl;
|
Chris@1347
|
319 #endif
|
Chris@151
|
320 return m_nameToChunkMenuMap[name]->addMenu(name);
|
Chris@151
|
321 }
|
Chris@151
|
322
|
Chris@151
|
323 void
|
Chris@151
|
324 SubdividingMenu::addMenu(const QString &name, QMenu *menu)
|
Chris@151
|
325 {
|
Chris@152
|
326 if (!m_entriesSet) {
|
Chris@152
|
327 m_pendingEntries[name] = menu;
|
Chris@152
|
328 return;
|
Chris@152
|
329 }
|
Chris@152
|
330
|
Chris@151
|
331 if (m_nameToChunkMenuMap.find(name) == m_nameToChunkMenuMap.end()) {
|
Chris@1347
|
332 #ifdef DEBUG_SUBDIVIDING_MENU
|
Chris@1347
|
333 cerr << "SubdividingMenu::addMenu(" << title() << " | " << name << "): not found in name-to-chunk map, adding to main menu" << endl;
|
Chris@1347
|
334 #endif
|
Chris@151
|
335 QMenu::addMenu(menu);
|
Chris@151
|
336 return;
|
Chris@151
|
337 }
|
Chris@151
|
338
|
Chris@1347
|
339 #ifdef DEBUG_SUBDIVIDING_MENU
|
Chris@1347
|
340 cerr << "SubdividingMenu::addMenu(" << title() << " | " << name << "): found in name-to-chunk map for menu " << m_nameToChunkMenuMap[name]->title() << endl;
|
Chris@1347
|
341 #endif
|
Chris@151
|
342 m_nameToChunkMenuMap[name]->addMenu(menu);
|
Chris@151
|
343 }
|
Chris@151
|
344
|