Chris@151: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ Chris@151: Chris@151: /* Chris@151: Sonic Visualiser Chris@151: An audio file viewer and annotation editor. Chris@151: Centre for Digital Music, Queen Mary, University of London. Chris@182: This file copyright 2006 QMUL. Chris@151: Chris@151: This program is free software; you can redistribute it and/or Chris@151: modify it under the terms of the GNU General Public License as Chris@151: published by the Free Software Foundation; either version 2 of the Chris@151: License, or (at your option) any later version. See the file Chris@151: COPYING included with this distribution for more information. Chris@151: */ Chris@151: Chris@151: #include "SubdividingMenu.h" Chris@151: Chris@151: #include Chris@151: Chris@682: #include "base/Debug.h" Chris@682: Chris@151: using std::set; Chris@151: using std::map; Chris@151: Chris@1347: //#define DEBUG_SUBDIVIDING_MENU 1 Chris@1347: Chris@807: SubdividingMenu::SubdividingMenu(int lowerLimit, int upperLimit, Chris@152: QWidget *parent) : Chris@152: QMenu(parent), Chris@152: m_lowerLimit(lowerLimit ? lowerLimit : 14), Chris@152: m_upperLimit(upperLimit ? upperLimit : (m_lowerLimit * 5) / 2), Chris@152: m_entriesSet(false) Chris@151: { Chris@1347: #ifdef DEBUG_SUBDIVIDING_MENU Chris@1347: cerr << "SubdividingMenu: constructed without title" << endl; Chris@1347: #endif Chris@151: } Chris@151: Chris@807: SubdividingMenu::SubdividingMenu(const QString &title, int lowerLimit, Chris@807: int upperLimit, QWidget *parent) : Chris@152: QMenu(title, parent), Chris@152: m_lowerLimit(lowerLimit ? lowerLimit : 14), Chris@152: m_upperLimit(upperLimit ? upperLimit : (m_lowerLimit * 5) / 2), Chris@152: m_entriesSet(false) Chris@151: { Chris@1347: #ifdef DEBUG_SUBDIVIDING_MENU Chris@1347: cerr << "SubdividingMenu: constructed with title \"" Chris@1347: << title << "\"" << endl; Chris@1347: #endif Chris@151: } Chris@151: Chris@151: SubdividingMenu::~SubdividingMenu() Chris@151: { Chris@152: for (map::iterator i = m_pendingEntries.begin(); Chris@152: i != m_pendingEntries.end(); ++i) { Chris@152: delete i->second; Chris@152: } Chris@151: } Chris@151: Chris@151: void Chris@151: SubdividingMenu::setEntries(const std::set &entries) Chris@151: { Chris@152: m_entriesSet = true; Chris@152: Chris@1347: #ifdef DEBUG_SUBDIVIDING_MENU Chris@1347: cerr << "SubdividingMenu::setEntries(" << title() << "): " Chris@1347: << entries.size() << " entries" << endl; Chris@1347: #endif Chris@1347: Chris@908: int total = int(entries.size()); Chris@151: Chris@152: if (total < m_upperLimit) return; Chris@151: Chris@807: int count = 0; Chris@151: QMenu *chunkMenu = new QMenu(); Chris@196: chunkMenu->setTearOffEnabled(isTearOffEnabled()); Chris@151: Chris@151: QString firstNameInChunk; Chris@151: QChar firstInitialInChunk; Chris@151: bool discriminateStartInitial = false; Chris@151: Chris@1347: // Re-sort using locale-aware comparator Chris@151: Chris@1347: auto comparator = [](QString s1, QString s2) -> bool { Chris@1347: return QString::localeAwareCompare(s1, s2) < 0; Chris@1347: }; Chris@1347: Chris@1358: set sortedEntries(comparator); Chris@1347: sortedEntries.insert(entries.begin(), entries.end()); Chris@1347: Chris@1347: for (auto j = sortedEntries.begin(); j != sortedEntries.end(); ++j) { Chris@1347: Chris@1347: #ifdef DEBUG_SUBDIVIDING_MENU Chris@1347: cerr << "SubdividingMenu::setEntries: entry is: " << j->toStdString() << endl; Chris@1347: #endif Chris@151: Chris@151: m_nameToChunkMenuMap[*j] = chunkMenu; Chris@151: Chris@1347: auto k = j; Chris@151: ++k; Chris@151: Chris@1347: QChar initial = (*j)[0].toUpper(); Chris@151: Chris@151: if (count == 0) { Chris@151: firstNameInChunk = *j; Chris@151: firstInitialInChunk = initial; Chris@1347: #ifdef DEBUG_SUBDIVIDING_MENU Chris@1347: cerr << "starting new chunk at initial " << initial << endl; Chris@1347: #endif Chris@151: } Chris@151: Chris@1347: #ifdef DEBUG_SUBDIVIDING_MENU Chris@1347: cerr << "count = "<< count << ", upper limit = " << m_upperLimit << endl; Chris@1347: #endif Chris@152: Chris@1347: bool lastInChunk = (k == sortedEntries.end() || Chris@152: (count >= m_lowerLimit-1 && Chris@152: (count == m_upperLimit || Chris@1347: (*k)[0].toUpper() != initial))); Chris@151: Chris@151: ++count; Chris@151: Chris@151: if (lastInChunk) { Chris@151: Chris@1347: bool discriminateEndInitial = (k != sortedEntries.end() && Chris@1347: (*k)[0].toUpper() == initial); Chris@151: Chris@151: bool initialsEqual = (firstInitialInChunk == initial); Chris@151: Chris@151: QString from = QString("%1").arg(firstInitialInChunk); Chris@151: if (discriminateStartInitial || Chris@151: (discriminateEndInitial && initialsEqual)) { Chris@151: from = firstNameInChunk.left(3); Chris@151: } Chris@151: Chris@151: QString to = QString("%1").arg(initial); Chris@151: if (discriminateEndInitial || Chris@151: (discriminateStartInitial && initialsEqual)) { Chris@151: to = j->left(3); Chris@151: } Chris@151: Chris@151: QString menuText; Chris@151: Chris@151: if (from == to) menuText = from; Chris@151: else menuText = tr("%1 - %2").arg(from).arg(to); Chris@151: Chris@151: discriminateStartInitial = discriminateEndInitial; Chris@151: Chris@151: chunkMenu->setTitle(menuText); Chris@151: Chris@151: QMenu::addMenu(chunkMenu); Chris@151: Chris@151: chunkMenu = new QMenu(); Chris@196: chunkMenu->setTearOffEnabled(isTearOffEnabled()); Chris@151: Chris@151: count = 0; Chris@151: } Chris@151: } Chris@151: Chris@151: if (count == 0) delete chunkMenu; Chris@151: } Chris@151: Chris@151: void Chris@152: SubdividingMenu::entriesAdded() Chris@152: { Chris@152: if (m_entriesSet) { Chris@1347: SVCERR << "ERROR: SubdividingMenu::entriesAdded: setEntries was also called -- should use one mechanism or the other, but not both" << endl; Chris@152: return; Chris@152: } Chris@152: Chris@152: set entries; Chris@1347: for (auto i: m_pendingEntries) { Chris@1347: entries.insert(i.first); Chris@1347: } Chris@1347: setEntries(entries); Chris@1347: Chris@1347: // Re-sort using locale-aware comparator (setEntries will do this Chris@1347: // again, for the set passed to it, but we need the same sorting Chris@1347: // for the subsequent loop in this function as well) Chris@1347: auto comparator = [](QString s1, QString s2) -> bool { Chris@1347: return QString::localeAwareCompare(s1, s2) < 0; Chris@1347: }; Chris@1358: set sortedEntries(comparator); Chris@1347: for (auto i: m_pendingEntries) { Chris@1347: sortedEntries.insert(i.first); Chris@152: } Chris@152: Chris@1347: for (QString entry: sortedEntries) { Chris@152: Chris@1347: QObject *obj = m_pendingEntries[entry]; Chris@1347: Chris@1347: QMenu *menu = dynamic_cast(obj); Chris@152: if (menu) { Chris@1347: addMenu(entry, menu); Chris@152: continue; Chris@152: } Chris@152: Chris@1347: QAction *action = dynamic_cast(obj); Chris@152: if (action) { Chris@1347: addAction(entry, action); Chris@152: continue; Chris@152: } Chris@152: } Chris@152: Chris@152: m_pendingEntries.clear(); Chris@152: } Chris@152: Chris@152: void Chris@151: SubdividingMenu::addAction(QAction *action) Chris@151: { Chris@151: QString name = action->text(); Chris@151: Chris@152: if (!m_entriesSet) { Chris@152: m_pendingEntries[name] = action; Chris@152: return; Chris@152: } Chris@152: Chris@151: if (m_nameToChunkMenuMap.find(name) == m_nameToChunkMenuMap.end()) { Chris@1347: #ifdef DEBUG_SUBDIVIDING_MENU Chris@1347: cerr << "SubdividingMenu::addAction(" << title() << " | " << name << "): not found in name-to-chunk map, adding to main menu" << endl; Chris@1347: #endif Chris@151: QMenu::addAction(action); Chris@151: return; Chris@151: } Chris@151: Chris@1347: #ifdef DEBUG_SUBDIVIDING_MENU Chris@1347: cerr << "SubdividingMenu::addAction(" << title() << " | " << name << "): found in name-to-chunk map for menu " << m_nameToChunkMenuMap[name]->title() << endl; Chris@1347: #endif Chris@151: m_nameToChunkMenuMap[name]->addAction(action); Chris@151: } Chris@151: Chris@151: QAction * Chris@151: SubdividingMenu::addAction(const QString &name) Chris@151: { Chris@152: if (!m_entriesSet) { Chris@152: QAction *action = new QAction(name, this); Chris@152: m_pendingEntries[name] = action; Chris@152: return action; Chris@152: } Chris@152: Chris@151: if (m_nameToChunkMenuMap.find(name) == m_nameToChunkMenuMap.end()) { Chris@1347: #ifdef DEBUG_SUBDIVIDING_MENU Chris@1347: cerr << "SubdividingMenu::addAction(" << title() << " | " << name << "): not found in name-to-chunk map, adding to main menu" << endl; Chris@1347: #endif Chris@151: return QMenu::addAction(name); Chris@151: } Chris@151: Chris@1347: #ifdef DEBUG_SUBDIVIDING_MENU Chris@1347: cerr << "SubdividingMenu::addAction(" << title() << " | " << name << "): found in name-to-chunk map for menu " << m_nameToChunkMenuMap[name]->title() << endl; Chris@1347: #endif Chris@151: return m_nameToChunkMenuMap[name]->addAction(name); Chris@151: } Chris@151: Chris@151: void Chris@151: SubdividingMenu::addAction(const QString &name, QAction *action) Chris@151: { Chris@152: if (!m_entriesSet) { Chris@152: m_pendingEntries[name] = action; Chris@152: return; Chris@152: } Chris@152: Chris@151: if (m_nameToChunkMenuMap.find(name) == m_nameToChunkMenuMap.end()) { Chris@1347: #ifdef DEBUG_SUBDIVIDING_MENU Chris@1347: cerr << "SubdividingMenu::addAction(" << title() << " | " << name << "): not found in name-to-chunk map, adding to main menu" << endl; Chris@1347: #endif Chris@151: QMenu::addAction(action); Chris@151: return; Chris@151: } Chris@151: Chris@1347: #ifdef DEBUG_SUBDIVIDING_MENU Chris@1347: cerr << "SubdividingMenu::addAction(" << title() << " | " << name << "): found in name-to-chunk map for menu " << m_nameToChunkMenuMap[name]->title() << endl; Chris@1347: #endif Chris@151: m_nameToChunkMenuMap[name]->addAction(action); Chris@151: } Chris@151: Chris@151: void Chris@151: SubdividingMenu::addMenu(QMenu *menu) Chris@151: { Chris@151: QString name = menu->title(); Chris@151: Chris@152: if (!m_entriesSet) { Chris@152: m_pendingEntries[name] = menu; Chris@152: return; Chris@152: } Chris@152: Chris@151: if (m_nameToChunkMenuMap.find(name) == m_nameToChunkMenuMap.end()) { Chris@1347: #ifdef DEBUG_SUBDIVIDING_MENU Chris@1347: cerr << "SubdividingMenu::addMenu(" << title() << " | " << name << "): not found in name-to-chunk map, adding to main menu" << endl; Chris@1347: #endif Chris@151: QMenu::addMenu(menu); Chris@151: return; Chris@151: } Chris@151: Chris@1347: #ifdef DEBUG_SUBDIVIDING_MENU Chris@1347: cerr << "SubdividingMenu::addMenu(" << title() << " | " << name << "): found in name-to-chunk map for menu " << m_nameToChunkMenuMap[name]->title() << endl; Chris@1347: #endif Chris@151: m_nameToChunkMenuMap[name]->addMenu(menu); Chris@151: } Chris@151: Chris@151: QMenu * Chris@151: SubdividingMenu::addMenu(const QString &name) Chris@151: { Chris@152: if (!m_entriesSet) { Chris@152: QMenu *menu = new QMenu(name, this); Chris@196: menu->setTearOffEnabled(isTearOffEnabled()); Chris@152: m_pendingEntries[name] = menu; Chris@152: return menu; Chris@152: } Chris@152: Chris@151: if (m_nameToChunkMenuMap.find(name) == m_nameToChunkMenuMap.end()) { Chris@1347: #ifdef DEBUG_SUBDIVIDING_MENU Chris@1347: cerr << "SubdividingMenu::addMenu(" << title() << " | " << name << "): not found in name-to-chunk map, adding to main menu" << endl; Chris@1347: #endif Chris@151: return QMenu::addMenu(name); Chris@151: } Chris@151: Chris@1347: #ifdef DEBUG_SUBDIVIDING_MENU Chris@1347: cerr << "SubdividingMenu::addMenu(" << title() << " | " << name << "): found in name-to-chunk map for menu " << m_nameToChunkMenuMap[name]->title() << endl; Chris@1347: #endif Chris@151: return m_nameToChunkMenuMap[name]->addMenu(name); Chris@151: } Chris@151: Chris@151: void Chris@151: SubdividingMenu::addMenu(const QString &name, QMenu *menu) Chris@151: { Chris@152: if (!m_entriesSet) { Chris@152: m_pendingEntries[name] = menu; Chris@152: return; Chris@152: } Chris@152: Chris@151: if (m_nameToChunkMenuMap.find(name) == m_nameToChunkMenuMap.end()) { Chris@1347: #ifdef DEBUG_SUBDIVIDING_MENU Chris@1347: cerr << "SubdividingMenu::addMenu(" << title() << " | " << name << "): not found in name-to-chunk map, adding to main menu" << endl; Chris@1347: #endif Chris@151: QMenu::addMenu(menu); Chris@151: return; Chris@151: } Chris@151: Chris@1347: #ifdef DEBUG_SUBDIVIDING_MENU Chris@1347: cerr << "SubdividingMenu::addMenu(" << title() << " | " << name << "): found in name-to-chunk map for menu " << m_nameToChunkMenuMap[name]->title() << endl; Chris@1347: #endif Chris@151: m_nameToChunkMenuMap[name]->addMenu(menu); Chris@151: } Chris@151: