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 <iostream>
Chris@151: 
Chris@682: #include "base/Debug.h"
Chris@682: 
Chris@151: using std::set;
Chris@151: using std::map;
Chris@151: 
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@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@151: }
Chris@151: 
Chris@151: SubdividingMenu::~SubdividingMenu()
Chris@151: {
Chris@152:     for (map<QString, QObject *>::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<QString> &entries)
Chris@151: {
Chris@152:     m_entriesSet = true;
Chris@152: 
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@151:     for (set<QString>::const_iterator j = entries.begin();
Chris@151:          j != entries.end();
Chris@151:          ++j) {
Chris@151: 
Chris@587: //        SVDEBUG << "SubdividingMenu::setEntries: j -> " << j->toStdString() << endl;
Chris@151: 
Chris@151:         m_nameToChunkMenuMap[*j] = chunkMenu;
Chris@151: 
Chris@151:         set<QString>::iterator k = j;
Chris@151:         ++k;
Chris@151: 
Chris@151:         QChar initial = (*j)[0];
Chris@151: 
Chris@151:         if (count == 0) {
Chris@151:             firstNameInChunk = *j;
Chris@151:             firstInitialInChunk = initial;
Chris@151:         }
Chris@151: 
Chris@682: //        cerr << "count = "<< count << ", upper limit = " << m_upperLimit << endl;
Chris@152: 
Chris@151:         bool lastInChunk = (k == entries.end() ||
Chris@152:                             (count >= m_lowerLimit-1 &&
Chris@152:                              (count == m_upperLimit ||
Chris@151:                               (*k)[0] != initial)));
Chris@151: 
Chris@151:         ++count;
Chris@151: 
Chris@151:         if (lastInChunk) {
Chris@151: 
Chris@151:             bool discriminateEndInitial = (k != entries.end() &&
Chris@151:                                            (*k)[0] == 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@682:         cerr << "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<QString> entries;
Chris@152:     for (map<QString, QObject *>::const_iterator i = m_pendingEntries.begin();
Chris@152:          i != m_pendingEntries.end(); ++i) {
Chris@152:         entries.insert(i->first);
Chris@152:     }
Chris@152:     
Chris@152:     setEntries(entries);
Chris@152: 
Chris@152:     for (map<QString, QObject *>::iterator i = m_pendingEntries.begin();
Chris@152:          i != m_pendingEntries.end(); ++i) {
Chris@152: 
Chris@152:         QMenu *menu = dynamic_cast<QMenu *>(i->second);
Chris@152:         if (menu) {
Chris@152:             addMenu(i->first, menu);
Chris@152:             continue;
Chris@152:         }
Chris@152: 
Chris@152:         QAction *action = dynamic_cast<QAction *>(i->second);
Chris@152:         if (action) {
Chris@152:             addAction(i->first, 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@587: //        SVDEBUG << "SubdividingMenu::addAction(" << name << "): not found in name-to-chunk map, adding to main menu" << endl;
Chris@151:         QMenu::addAction(action);
Chris@151:         return;
Chris@151:     }
Chris@151: 
Chris@587: //    SVDEBUG << "SubdividingMenu::addAction(" << name << "): found in name-to-chunk map for menu " << m_nameToChunkMenuMap[name]->title() << endl;
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@587: //        SVDEBUG << "SubdividingMenu::addAction(" << name << "): not found in name-to-chunk map, adding to main menu" << endl;
Chris@151:         return QMenu::addAction(name);
Chris@151:     }
Chris@151: 
Chris@587: //    SVDEBUG << "SubdividingMenu::addAction(" << name << "): found in name-to-chunk map for menu " << m_nameToChunkMenuMap[name]->title() << endl;
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@587: //        SVDEBUG << "SubdividingMenu::addAction(" << name << "): not found in name-to-chunk map, adding to main menu" << endl;
Chris@151:         QMenu::addAction(action);
Chris@151:         return;
Chris@151:     }
Chris@151: 
Chris@587: //    SVDEBUG << "SubdividingMenu::addAction(" << name << "): found in name-to-chunk map for menu " << m_nameToChunkMenuMap[name]->title() << endl;
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@587: //        SVDEBUG << "SubdividingMenu::addMenu(" << name << "): not found in name-to-chunk map, adding to main menu" << endl;
Chris@151:         QMenu::addMenu(menu);
Chris@151:         return;
Chris@151:     }
Chris@151: 
Chris@587: //    SVDEBUG << "SubdividingMenu::addMenu(" << name << "): found in name-to-chunk map for menu " << m_nameToChunkMenuMap[name]->title() << endl;
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@587: //        SVDEBUG << "SubdividingMenu::addMenu(" << name << "): not found in name-to-chunk map, adding to main menu" << endl;
Chris@151:         return QMenu::addMenu(name);
Chris@151:     }
Chris@151: 
Chris@587: //    SVDEBUG << "SubdividingMenu::addMenu(" << name << "): found in name-to-chunk map for menu " << m_nameToChunkMenuMap[name]->title() << endl;
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@587: //        SVDEBUG << "SubdividingMenu::addMenu(" << name << "): not found in name-to-chunk map, adding to main menu" << endl;
Chris@151:         QMenu::addMenu(menu);
Chris@151:         return;
Chris@151:     }
Chris@151: 
Chris@587: //    SVDEBUG << "SubdividingMenu::addMenu(" << name << "): found in name-to-chunk map for menu " << m_nameToChunkMenuMap[name]->title() << endl;
Chris@151:     m_nameToChunkMenuMap[name]->addMenu(menu);
Chris@151: }
Chris@151: