view widgets/SubdividingMenu.cpp @ 1553:76e4302a3fc2

Fix note numbering - ensure stable across whole track (as it used to be, but without scanning all notes in paint in order to do that)
author Chris Cannam
date Fri, 22 Nov 2019 14:12:50 +0000
parents 53fe33b00770
children
line wrap: on
line source
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */

/*
    Sonic Visualiser
    An audio file viewer and annotation editor.
    Centre for Digital Music, Queen Mary, University of London.
    This file copyright 2006 QMUL.
    
    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License as
    published by the Free Software Foundation; either version 2 of the
    License, or (at your option) any later version.  See the file
    COPYING included with this distribution for more information.
*/

#include "SubdividingMenu.h"

#include <iostream>

#include "base/Debug.h"

using std::set;
using std::map;

//#define DEBUG_SUBDIVIDING_MENU 1

SubdividingMenu::SubdividingMenu(int lowerLimit, int upperLimit,
                                 QWidget *parent) :
    QMenu(parent),
    m_lowerLimit(lowerLimit ? lowerLimit : 14),
    m_upperLimit(upperLimit ? upperLimit : (m_lowerLimit * 5) / 2),
    m_entriesSet(false)
{
#ifdef DEBUG_SUBDIVIDING_MENU
    cerr << "SubdividingMenu: constructed without title" << endl;
#endif
}

SubdividingMenu::SubdividingMenu(const QString &title, int lowerLimit,
                                 int upperLimit, QWidget *parent) :
    QMenu(title, parent),
    m_lowerLimit(lowerLimit ? lowerLimit : 14),
    m_upperLimit(upperLimit ? upperLimit : (m_lowerLimit * 5) / 2),
    m_entriesSet(false)
{
#ifdef DEBUG_SUBDIVIDING_MENU
    cerr << "SubdividingMenu: constructed with title \""
         << title << "\"" << endl;
#endif
}

SubdividingMenu::~SubdividingMenu()
{
    for (map<QString, QObject *>::iterator i = m_pendingEntries.begin();
         i != m_pendingEntries.end(); ++i) {
        delete i->second;
    }
}

void
SubdividingMenu::setEntries(const std::set<QString> &entries)
{
    m_entriesSet = true;

#ifdef DEBUG_SUBDIVIDING_MENU
    cerr << "SubdividingMenu::setEntries(" << title() << "): "
         << entries.size() << " entries" << endl;
#endif

    int total = int(entries.size());
        
    if (total < m_upperLimit) return;

    int count = 0;
    QMenu *chunkMenu = new QMenu();
    chunkMenu->setTearOffEnabled(isTearOffEnabled());

    QString firstNameInChunk;
    QChar firstInitialInChunk;
    bool discriminateStartInitial = false;

    // Re-sort using locale-aware comparator

    auto comparator = [](QString s1, QString s2) -> bool {
                          return QString::localeAwareCompare(s1, s2) < 0;
                      };
    
    set<QString, decltype(comparator)> sortedEntries(comparator);
    sortedEntries.insert(entries.begin(), entries.end());
    
    for (auto j = sortedEntries.begin(); j != sortedEntries.end(); ++j) {

#ifdef DEBUG_SUBDIVIDING_MENU
        cerr << "SubdividingMenu::setEntries: entry is: " << j->toStdString() << endl;
#endif

        m_nameToChunkMenuMap[*j] = chunkMenu;

        auto k = j;
        ++k;

        QChar initial = (*j)[0].toUpper();

        if (count == 0) {
            firstNameInChunk = *j;
            firstInitialInChunk = initial;
#ifdef DEBUG_SUBDIVIDING_MENU
            cerr << "starting new chunk at initial " << initial << endl;
#endif
        }

#ifdef DEBUG_SUBDIVIDING_MENU
        cerr << "count = "<< count << ", upper limit = " << m_upperLimit << endl;
#endif

        bool lastInChunk = (k == sortedEntries.end() ||
                            (count >= m_lowerLimit-1 &&
                             (count == m_upperLimit ||
                              (*k)[0].toUpper() != initial)));

        ++count;

        if (lastInChunk) {

            bool discriminateEndInitial = (k != sortedEntries.end() &&
                                           (*k)[0].toUpper() == initial);

            bool initialsEqual = (firstInitialInChunk == initial);

            QString from = QString("%1").arg(firstInitialInChunk);
            if (discriminateStartInitial ||
                (discriminateEndInitial && initialsEqual)) {
                from = firstNameInChunk.left(3);
            }

            QString to = QString("%1").arg(initial);
            if (discriminateEndInitial ||
                (discriminateStartInitial && initialsEqual)) {
                to = j->left(3);
            }

            QString menuText;
            
            if (from == to) menuText = from;
            else menuText = tr("%1 - %2").arg(from).arg(to);
            
            discriminateStartInitial = discriminateEndInitial;

            chunkMenu->setTitle(menuText);
                
            QMenu::addMenu(chunkMenu);
            
            chunkMenu = new QMenu();
            chunkMenu->setTearOffEnabled(isTearOffEnabled());
            
            count = 0;
        }
    }
    
    if (count == 0) delete chunkMenu;
}

void
SubdividingMenu::entriesAdded()
{
    if (m_entriesSet) {
        SVCERR << "ERROR: SubdividingMenu::entriesAdded: setEntries was also called -- should use one mechanism or the other, but not both" << endl;
        return;
    }
    
    set<QString> entries;
    for (auto i: m_pendingEntries) {
        entries.insert(i.first);
    }
    setEntries(entries);

    // Re-sort using locale-aware comparator (setEntries will do this
    // again, for the set passed to it, but we need the same sorting
    // for the subsequent loop in this function as well)
    auto comparator = [](QString s1, QString s2) -> bool {
                          return QString::localeAwareCompare(s1, s2) < 0;
                      };
    set<QString, decltype(comparator)> sortedEntries(comparator);
    for (auto i: m_pendingEntries) {
        sortedEntries.insert(i.first);
    }
    
    for (QString entry: sortedEntries) {

        QObject *obj = m_pendingEntries[entry];
        
        QMenu *menu = dynamic_cast<QMenu *>(obj);
        if (menu) {
            addMenu(entry, menu);
            continue;
        }

        QAction *action = dynamic_cast<QAction *>(obj);
        if (action) {
            addAction(entry, action);
            continue;
        }
    }

    m_pendingEntries.clear();
}

void
SubdividingMenu::addAction(QAction *action)
{
    QString name = action->text();

    if (!m_entriesSet) {
        m_pendingEntries[name] = action;
        return;
    }

    if (m_nameToChunkMenuMap.find(name) == m_nameToChunkMenuMap.end()) {
#ifdef DEBUG_SUBDIVIDING_MENU
        cerr << "SubdividingMenu::addAction(" << title() << " | " << name << "): not found in name-to-chunk map, adding to main menu" << endl;
#endif
        QMenu::addAction(action);
        return;
    }

#ifdef DEBUG_SUBDIVIDING_MENU
    cerr << "SubdividingMenu::addAction(" << title() << " | " << name << "): found in name-to-chunk map for menu " << m_nameToChunkMenuMap[name]->title() << endl;
#endif
    m_nameToChunkMenuMap[name]->addAction(action);
}

QAction *
SubdividingMenu::addAction(const QString &name)
{
    if (!m_entriesSet) {
        QAction *action = new QAction(name, this);
        m_pendingEntries[name] = action;
        return action;
    }

    if (m_nameToChunkMenuMap.find(name) == m_nameToChunkMenuMap.end()) {
#ifdef DEBUG_SUBDIVIDING_MENU
        cerr << "SubdividingMenu::addAction(" << title() << " | " << name << "): not found in name-to-chunk map, adding to main menu" << endl;
#endif
        return QMenu::addAction(name);
    }

#ifdef DEBUG_SUBDIVIDING_MENU
    cerr << "SubdividingMenu::addAction(" << title() << " | " << name << "): found in name-to-chunk map for menu " << m_nameToChunkMenuMap[name]->title() << endl;
#endif
    return m_nameToChunkMenuMap[name]->addAction(name);
}

void
SubdividingMenu::addAction(const QString &name, QAction *action)
{
    if (!m_entriesSet) {
        m_pendingEntries[name] = action;
        return;
    }

    if (m_nameToChunkMenuMap.find(name) == m_nameToChunkMenuMap.end()) {
#ifdef DEBUG_SUBDIVIDING_MENU
        cerr << "SubdividingMenu::addAction(" << title() << " | " << name << "): not found in name-to-chunk map, adding to main menu" << endl;
#endif
        QMenu::addAction(action);
        return;
    }

#ifdef DEBUG_SUBDIVIDING_MENU
    cerr << "SubdividingMenu::addAction(" << title() << " | " << name << "): found in name-to-chunk map for menu " << m_nameToChunkMenuMap[name]->title() << endl;
#endif
    m_nameToChunkMenuMap[name]->addAction(action);
}

void
SubdividingMenu::addMenu(QMenu *menu)
{
    QString name = menu->title();

    if (!m_entriesSet) {
        m_pendingEntries[name] = menu;
        return;
    }

    if (m_nameToChunkMenuMap.find(name) == m_nameToChunkMenuMap.end()) {
#ifdef DEBUG_SUBDIVIDING_MENU
        cerr << "SubdividingMenu::addMenu(" << title() << " | " << name << "): not found in name-to-chunk map, adding to main menu" << endl;
#endif
        QMenu::addMenu(menu);
        return;
    }

#ifdef DEBUG_SUBDIVIDING_MENU
    cerr << "SubdividingMenu::addMenu(" << title() << " | " << name << "): found in name-to-chunk map for menu " << m_nameToChunkMenuMap[name]->title() << endl;
#endif
    m_nameToChunkMenuMap[name]->addMenu(menu);
}

QMenu *
SubdividingMenu::addMenu(const QString &name)
{
    if (!m_entriesSet) {
        QMenu *menu = new QMenu(name, this);
        menu->setTearOffEnabled(isTearOffEnabled());
        m_pendingEntries[name] = menu;
        return menu;
    }

    if (m_nameToChunkMenuMap.find(name) == m_nameToChunkMenuMap.end()) {
#ifdef DEBUG_SUBDIVIDING_MENU
        cerr << "SubdividingMenu::addMenu(" << title() << " | " << name << "): not found in name-to-chunk map, adding to main menu" << endl;
#endif
        return QMenu::addMenu(name);
    }

#ifdef DEBUG_SUBDIVIDING_MENU
    cerr << "SubdividingMenu::addMenu(" << title() << " | " << name << "): found in name-to-chunk map for menu " << m_nameToChunkMenuMap[name]->title() << endl;
#endif
    return m_nameToChunkMenuMap[name]->addMenu(name);
}

void
SubdividingMenu::addMenu(const QString &name, QMenu *menu)
{
    if (!m_entriesSet) {
        m_pendingEntries[name] = menu;
        return;
    }

    if (m_nameToChunkMenuMap.find(name) == m_nameToChunkMenuMap.end()) {
#ifdef DEBUG_SUBDIVIDING_MENU
        cerr << "SubdividingMenu::addMenu(" << title() << " | " << name << "): not found in name-to-chunk map, adding to main menu" << endl;
#endif
        QMenu::addMenu(menu);
        return;
    }

#ifdef DEBUG_SUBDIVIDING_MENU
    cerr << "SubdividingMenu::addMenu(" << title() << " | " << name << "): found in name-to-chunk map for menu " << m_nameToChunkMenuMap[name]->title() << endl;
#endif
    m_nameToChunkMenuMap[name]->addMenu(menu);
}