annotate widgets/SubdividingMenu.cpp @ 1613:fe9a643b83bf

Merge from branch csv-import-headers
author Chris Cannam
date Thu, 18 Jun 2020 13:45:11 +0100
parents 53fe33b00770
children
rev   line source
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