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