annotate widgets/SubdividingMenu.cpp @ 1551:e79731086b0f

Fixes to NoteLayer, particularly to calculation of vertical scale when model unit is not Hz. To avoid inconsistency we now behave as if the unit is always Hz from the point of view of the external API and display, converting at the point where we obtain values from the events themselves. Also various fixes to editing.
author Chris Cannam
date Thu, 21 Nov 2019 14:02:57 +0000
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