changeset 34:8ad306d8a568

* Pull transforms out of Layer menu (again) and into a separate Transforms menu * Add Recent Transforms submenu * Add effects and generators to the transforms menu (not yet implemented) as well as analysis plugins and data-from-effects (control output ports) * Add a nice dictionary-volume-style alphabetic subdivision of plugin names in plugins By Name menus
author Chris Cannam
date Fri, 22 Sep 2006 16:12:23 +0000
parents 544ab25d2372
children 06787742542a
files main/MainWindow.cpp main/MainWindow.h transform/RealTimePluginTransform.cpp transform/RealTimePluginTransform.h transform/TransformFactory.cpp transform/TransformFactory.h
diffstat 6 files changed, 275 insertions(+), 81 deletions(-) [+]
line wrap: on
line diff
--- a/main/MainWindow.cpp	Thu Sep 21 16:43:50 2006 +0000
+++ b/main/MainWindow.cpp	Fri Sep 22 16:12:23 2006 +0000
@@ -100,12 +100,18 @@
     m_timeRulerLayer(0),
     m_playSource(0),
     m_playTarget(0),
+    m_recentFiles("RecentFiles"),
+    m_recentTransforms("RecentTransforms"),
     m_mainMenusCreated(false),
     m_paneMenu(0),
     m_layerMenu(0),
+    m_transformsMenu(0),
     m_existingLayersMenu(0),
+    m_recentFilesMenu(0),
+    m_recentTransformsMenu(0),
     m_rightButtonMenu(0),
     m_rightButtonLayerMenu(0),
+    m_rightButtonTransformsMenu(0),
     m_documentModified(false),
     m_preferencesDialog(0)
 {
@@ -250,6 +256,13 @@
         m_rightButtonMenu->addSeparator();
     }
 
+    if (m_rightButtonTransformsMenu) {
+        m_rightButtonTransformsMenu->clear();
+    } else {
+        m_rightButtonTransformsMenu = m_rightButtonMenu->addMenu(tr("&Transform"));
+        m_rightButtonMenu->addSeparator();
+    }
+
     if (!m_mainMenusCreated) {
 
         CommandHistory::getInstance()->registerMenu(m_rightButtonMenu);
@@ -337,9 +350,8 @@
 
 	menu->addSeparator();
         m_recentFilesMenu = menu->addMenu(tr("&Recent Files"));
-        menu->addMenu(m_recentFilesMenu);
         setupRecentFilesMenu();
-        connect(RecentFiles::getInstance(), SIGNAL(recentFilesChanged()),
+        connect(&m_recentFiles, SIGNAL(recentChanged()),
                 this, SLOT(setupRecentFilesMenu()));
 
 	menu->addSeparator();
@@ -565,13 +577,20 @@
     }
 
     if (m_layerMenu) {
-	m_layerTransformActions.clear();
 	m_layerActions.clear();
 	m_layerMenu->clear();
     } else {
 	m_layerMenu = menuBar()->addMenu(tr("&Layer"));
     }
 
+    if (m_transformsMenu) {
+        m_transformActions.clear();
+        m_transformActionsReverse.clear();
+        m_transformsMenu->clear();
+    } else {
+	m_transformsMenu = menuBar()->addMenu(tr("&Transform"));
+    }
+
     TransformFactory::TransformList transforms =
 	TransformFactory::getInstance()->getAllTransforms();
 
@@ -584,16 +603,24 @@
     map<QString, QMenu *> byPluginNameMenus;
     map<QString, map<QString, QMenu *> > pluginNameMenus;
 
+    m_recentTransformsMenu = m_transformsMenu->addMenu(tr("&Recent Transforms"));
+    m_rightButtonTransformsMenu->addMenu(m_recentTransformsMenu);
+    connect(&m_recentTransforms, SIGNAL(recentChanged()),
+            this, SLOT(setupRecentTransformsMenu()));
+
+    m_transformsMenu->addSeparator();
+    m_rightButtonTransformsMenu->addSeparator();
+    
     for (vector<QString>::iterator i = types.begin(); i != types.end(); ++i) {
 
         if (i != types.begin()) {
-            m_layerMenu->addSeparator();
-            m_rightButtonLayerMenu->addSeparator();
+            m_transformsMenu->addSeparator();
+            m_rightButtonTransformsMenu->addSeparator();
         }
 
         QString byCategoryLabel = tr("%1 by Category").arg(*i);
-        QMenu *byCategoryMenu = m_layerMenu->addMenu(byCategoryLabel);
-        m_rightButtonLayerMenu->addMenu(byCategoryMenu);
+        QMenu *byCategoryMenu = m_transformsMenu->addMenu(byCategoryLabel);
+        m_rightButtonTransformsMenu->addMenu(byCategoryMenu);
 
         vector<QString> categories = 
             TransformFactory::getInstance()->getTransformCategories(*i);
@@ -630,9 +657,13 @@
             }
         }
 
+        QString byPluginNameLabel = tr("%1 by Plugin Name").arg(*i);
+        byPluginNameMenus[*i] = m_transformsMenu->addMenu(byPluginNameLabel);
+        m_rightButtonTransformsMenu->addMenu(byPluginNameMenus[*i]);
+
         QString byMakerLabel = tr("%1 by Maker").arg(*i);
-        QMenu *byMakerMenu = m_layerMenu->addMenu(byMakerLabel);
-        m_rightButtonLayerMenu->addMenu(byMakerMenu);
+        QMenu *byMakerMenu = m_transformsMenu->addMenu(byMakerLabel);
+        m_rightButtonTransformsMenu->addMenu(byMakerMenu);
 
         vector<QString> makers = 
             TransformFactory::getInstance()->getTransformMakers(*i);
@@ -645,12 +676,95 @@
             
             makerMenus[*i][maker] = byMakerMenu->addMenu(maker);
         }
-
-        QString byPluginNameLabel = tr("%1 by Plugin Name").arg(*i);
-        byPluginNameMenus[*i] = m_layerMenu->addMenu(byPluginNameLabel);
-        m_rightButtonLayerMenu->addMenu(byPluginNameMenus[*i]);
     }
 
+    map<QString, set<QString> > pluginNameLists;
+    map<QString, map<QString, QMenu *> > pluginNameToChunkMenuMap;
+
+    for (unsigned int i = 0; i < transforms.size(); ++i) {
+	QString description = transforms[i].description;
+	if (description == "") description = transforms[i].name;
+        QString type = transforms[i].type;
+        QString pluginName = description.section(": ", 0, 0);
+        pluginNameLists[type].insert(pluginName);
+    }
+
+    for (vector<QString>::iterator i = types.begin(); i != types.end(); ++i) {
+
+        size_t total = pluginNameLists[*i].size();
+        size_t chunk = 14;
+        
+        if (total < (chunk * 3) / 2) continue;
+
+        size_t count = 0;
+        QMenu *chunkMenu = new QMenu();
+
+        QString firstNameInChunk;
+        QChar firstInitialInChunk;
+        bool discriminateStartInitial = false;
+
+        for (set<QString>::iterator j = pluginNameLists[*i].begin();
+             j != pluginNameLists[*i].end();
+             ++j) {
+
+            pluginNameToChunkMenuMap[*i][*j] = chunkMenu;
+
+            set<QString>::iterator k = j;
+            ++k;
+
+            QChar initial = (*j)[0];
+
+            if (count == 0) {
+                firstNameInChunk = *j;
+                firstInitialInChunk = initial;
+            }
+
+            bool lastInChunk = (k == pluginNameLists[*i].end() ||
+                                (count >= chunk-1 &&
+                                 (count == (5*chunk) / 2 ||
+                                  (*k)[0] != initial)));
+
+            ++count;
+
+            if (lastInChunk) {
+
+                bool discriminateEndInitial = (k != pluginNameLists[*i].end() &&
+                                               (*k)[0] == 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);
+                
+                byPluginNameMenus[*i]->addMenu(chunkMenu);
+
+                chunkMenu = new QMenu();
+
+                count = 0;
+            }
+        }
+
+        if (count == 0) delete chunkMenu;
+    }
+    
     for (unsigned int i = 0; i < transforms.size(); ++i) {
 	
 	QString description = transforms[i].description;
@@ -669,7 +783,8 @@
 
 	action = new QAction(tr("%1...").arg(description), this);
 	connect(action, SIGNAL(triggered()), this, SLOT(addLayer()));
-	m_layerTransformActions[action] = transforms[i].name;
+	m_transformActions[action] = transforms[i].name;
+        m_transformActionsReverse[transforms[i].name] = action;
 	connect(this, SIGNAL(canAddLayer(bool)), action, SLOT(setEnabled(bool)));
 
         if (categoryMenus[type].find(category) == categoryMenus[type].end()) {
@@ -692,17 +807,22 @@
 
         action = new QAction(tr("%1...").arg(output == "" ? pluginName : output), this);
         connect(action, SIGNAL(triggered()), this, SLOT(addLayer()));
-        m_layerTransformActions[action] = transforms[i].name;
+        m_transformActions[action] = transforms[i].name;
         connect(this, SIGNAL(canAddLayer(bool)), action, SLOT(setEnabled(bool)));
 
+//        cerr << "Transform: \"" << name.toStdString() << "\": plugin name \"" << pluginName.toStdString() << "\"" << endl;
+
         if (pluginNameMenus[type].find(pluginName) ==
             pluginNameMenus[type].end()) {
 
+            QMenu *parentMenu = pluginNameToChunkMenuMap[type][pluginName];
+            if (!parentMenu) parentMenu = byPluginNameMenus[type];
+
             if (output == "") {
-                byPluginNameMenus[type]->addAction(action);
+                parentMenu->addAction(action);
             } else {
-                pluginNameMenus[type][pluginName] =
-                    byPluginNameMenus[type]->addMenu(pluginName);
+                pluginNameMenus[type][pluginName] = 
+                    parentMenu->addMenu(pluginName);
                 connect(this, SIGNAL(canAddLayer(bool)),
                         pluginNameMenus[type][pluginName],
                         SLOT(setEnabled(bool)));
@@ -715,7 +835,7 @@
         }
     }
 
-    m_rightButtonLayerMenu->addSeparator();
+    setupRecentTransformsMenu();
 
     menu = m_paneMenu;
 
@@ -731,7 +851,7 @@
 
     menu = m_layerMenu;
 
-    menu->addSeparator();
+//    menu->addSeparator();
 
     LayerFactory::LayerTypeSet emptyLayerTypes =
 	LayerFactory::getInstance()->getValidEmptyLayerTypes();
@@ -988,7 +1108,7 @@
 MainWindow::setupRecentFilesMenu()
 {
     m_recentFilesMenu->clear();
-    vector<QString> files = RecentFiles::getInstance()->getRecentFiles();
+    vector<QString> files = m_recentFiles.getRecent();
     for (size_t i = 0; i < files.size(); ++i) {
 	QAction *action = new QAction(files[i], this);
 	connect(action, SIGNAL(triggered()), this, SLOT(openRecentFile()));
@@ -997,6 +1117,24 @@
 }
 
 void
+MainWindow::setupRecentTransformsMenu()
+{
+    m_recentTransformsMenu->clear();
+    vector<QString> transforms = m_recentTransforms.getRecent();
+    for (size_t i = 0; i < transforms.size(); ++i) {
+        TransformActionReverseMap::iterator ti =
+            m_transformActionsReverse.find(transforms[i]);
+        if (ti == m_transformActionsReverse.end()) {
+            std::cerr << "WARNING: MainWindow::setupRecentTransformsMenu: "
+                      << "Unknown transform \"" << transforms[i].toStdString()
+                      << "\" in recent transforms list" << std::endl;
+            continue;
+        }
+	m_recentTransformsMenu->addAction(ti->second);
+    }
+}
+
+void
 MainWindow::setupExistingLayersMenu()
 {
     if (!m_existingLayersMenu) return; // should have been created by setupMenus
@@ -1702,7 +1840,7 @@
 
     if (ok) {
         if (!multiple) {
-            RecentFiles::getInstance()->addFile(path);
+            m_recentFiles.addFile(path);
         }
     } else {
 	QMessageBox::critical(this, tr("Failed to write file"), error);
@@ -1783,7 +1921,7 @@
             return false;
         }
 
-        RecentFiles::getInstance()->addFile(path);
+        m_recentFiles.addFile(path);
         return true;
         
     } else {
@@ -1794,7 +1932,7 @@
             Layer *newLayer = m_document->createImportedLayer(model);
             if (newLayer) {
                 m_document->addLayerToView(pane, newLayer);
-                RecentFiles::getInstance()->addFile(path);
+                m_recentFiles.addFile(path);
                 return true;
             }
         }
@@ -1862,7 +2000,7 @@
     if (error != "") {
         QMessageBox::critical(this, tr("Failed to write file"), error);
     } else {
-        RecentFiles::getInstance()->addFile(path);
+        m_recentFiles.addFile(path);
     }
 }
 
@@ -1970,7 +2108,7 @@
     }
 
     updateMenuStates();
-    RecentFiles::getInstance()->addFile(path);
+    m_recentFiles.addFile(path);
 
     return true;
 }
@@ -2275,7 +2413,7 @@
 	CommandHistory::getInstance()->documentSaved();
 	m_documentModified = false;
 	updateMenuStates();
-        RecentFiles::getInstance()->addFile(path);
+        m_recentFiles.addFile(path);
     } else {
 	setWindowTitle(tr("Sonic Visualiser"));
     }
@@ -2329,7 +2467,7 @@
             .arg(QProcess().pid());
         QString fpath = QDir(svDir).filePath(fname);
         if (saveSessionFile(fpath)) {
-            RecentFiles::getInstance()->addFile(fpath);
+            m_recentFiles.addFile(fpath);
             return true;
         } else {
             return false;
@@ -2438,7 +2576,7 @@
 	m_sessionFile = path;
 	CommandHistory::getInstance()->documentSaved();
 	documentRestored();
-        RecentFiles::getInstance()->addFile(path);
+        m_recentFiles.addFile(path);
     }
 }
 
@@ -2855,9 +2993,9 @@
 	return;
     }
 
-    TransformActionMap::iterator i = m_layerTransformActions.find(action);
-
-    if (i == m_layerTransformActions.end()) {
+    TransformActionMap::iterator i = m_transformActions.find(action);
+
+    if (i == m_transformActions.end()) {
 
 	LayerActionMap::iterator i = m_layerActions.find(action);
 	
@@ -2924,6 +3062,7 @@
     if (newLayer) {
         m_document->addLayerToView(pane, newLayer);
         m_document->setChannel(newLayer, context.channel);
+        m_recentTransforms.add(transform);
     }
 
     updateMenuStates();
@@ -3170,7 +3309,7 @@
     QMessageBox::warning
         (this,
          tr("Failed to generate layer"),
-         tr("The layer transform \"%1\" failed to run.\nThis probably means that a plugin failed to initialise.")
+         tr("Failed to generate a derived layer.\n\nThe layer transform \"%1\" failed.\n\nThis probably means that a plugin failed to initialise, perhaps because it\nrejected the processing block size that was requested.")
          .arg(transformName),
          QMessageBox::Ok, 0);
 }
@@ -3181,7 +3320,7 @@
     QMessageBox::warning
         (this,
          tr("Failed to regenerate layer"),
-         tr("Failed to regenerate derived layer \"%1\".\nThe layer transform \"%2\" failed to run.\nThis probably means the layer used a plugin that is not currently available.")
+         tr("Failed to regenerate derived layer \"%1\".\n\nThe layer transform \"%2\" failed to run.\n\nThis probably means the layer used a plugin that is not currently available.")
          .arg(layerName).arg(transformName),
          QMessageBox::Ok, 0);
 }
--- a/main/MainWindow.h	Thu Sep 21 16:43:50 2006 +0000
+++ b/main/MainWindow.h	Fri Sep 22 16:12:23 2006 +0000
@@ -24,6 +24,7 @@
 #include "base/Command.h"
 #include "view/ViewManager.h"
 #include "base/PropertyContainer.h"
+#include "base/RecentFiles.h"
 #include "layer/LayerFactory.h"
 #include "transform/Transform.h"
 #include "document/SVFileReader.h"
@@ -189,6 +190,7 @@
     void preferenceChanged(PropertyContainer::PropertyName);
 
     void setupRecentFilesMenu();
+    void setupRecentTransformsMenu();
 
     void showLayerTree();
 
@@ -215,13 +217,19 @@
     AudioCallbackPlaySource *m_playSource;
     AudioCallbackPlayTarget *m_playTarget;
 
+    RecentFiles              m_recentFiles;
+    RecentFiles              m_recentTransforms;
+
     bool                     m_mainMenusCreated;
     QMenu                   *m_paneMenu;
     QMenu                   *m_layerMenu;
+    QMenu                   *m_transformsMenu;
     QMenu                   *m_existingLayersMenu;
     QMenu                   *m_recentFilesMenu;
+    QMenu                   *m_recentTransformsMenu;
     QMenu                   *m_rightButtonMenu;
     QMenu                   *m_rightButtonLayerMenu;
+    QMenu                   *m_rightButtonTransformsMenu;
 
     bool                     m_documentModified;
 
@@ -243,7 +251,10 @@
     PaneActionMap m_paneActions;
 
     typedef std::map<QAction *, TransformName> TransformActionMap;
-    TransformActionMap m_layerTransformActions;
+    TransformActionMap m_transformActions;
+
+    typedef std::map<TransformName, QAction *> TransformActionReverseMap;
+    TransformActionReverseMap m_transformActionsReverse;
 
     typedef std::map<QAction *, LayerFactory::LayerType> LayerActionMap;
     LayerActionMap m_layerActions;
--- a/transform/RealTimePluginTransform.cpp	Thu Sep 21 16:43:50 2006 +0000
+++ b/transform/RealTimePluginTransform.cpp	Fri Sep 22 16:12:23 2006 +0000
@@ -66,17 +66,24 @@
         PluginXml(m_plugin).setParametersFromXml(configurationXml);
     }
 
-    if (m_outputNo >= m_plugin->getControlOutputCount()) {
+    if (m_outputNo >= 0 && m_outputNo >= m_plugin->getControlOutputCount()) {
         std::cerr << "RealTimePluginTransform: Plugin has fewer than desired " << m_outputNo << " control outputs" << std::endl;
         return;
     }
+
+    if (m_outputNo == -1) {
+
+        //!!! process audio!
+
+    } else {
 	
-    SparseTimeValueModel *model = new SparseTimeValueModel
-        (input->getSampleRate(), m_context.blockSize, 0.0, 0.0, false);
+        SparseTimeValueModel *model = new SparseTimeValueModel
+            (input->getSampleRate(), m_context.blockSize, 0.0, 0.0, false);
 
-    if (units != "") model->setScaleUnits(units);
+        if (units != "") model->setScaleUnits(units);
 
-    m_output = model;
+        m_output = model;
+    }
 }
 
 RealTimePluginTransform::~RealTimePluginTransform()
--- a/transform/RealTimePluginTransform.h	Thu Sep 21 16:43:50 2006 +0000
+++ b/transform/RealTimePluginTransform.h	Fri Sep 22 16:12:23 2006 +0000
@@ -29,7 +29,7 @@
                             const ExecutionContext &context,
 			    QString configurationXml = "",
                             QString units = "",
-			    int output = 0);
+			    int output = -1); // -1 -> audio, 0+ -> data
     virtual ~RealTimePluginTransform();
 
 protected:
--- a/transform/TransformFactory.cpp	Thu Sep 21 16:43:50 2006 +0000
+++ b/transform/TransformFactory.cpp	Fri Sep 22 16:12:23 2006 +0000
@@ -51,10 +51,16 @@
 {
     if (m_transforms.empty()) populateTransforms();
 
-    TransformList list;
+    std::set<TransformDesc> dset;
     for (TransformDescriptionMap::const_iterator i = m_transforms.begin();
 	 i != m_transforms.end(); ++i) {
-	list.push_back(i->second);
+	dset.insert(i->second);
+    }
+
+    TransformList list;
+    for (std::set<TransformDesc>::const_iterator i = dset.begin();
+	 i != dset.end(); ++i) {
+	list.push_back(*i);
     }
 
     return list;
@@ -233,7 +239,7 @@
                                  !plugin->getParameterDescriptors().empty());
 
 	    transforms[transformName] = 
-                TransformDesc(tr("Analysis Plugins"),
+                TransformDesc(tr("Analysis"),
                               category,
                               transformName,
                               userDescription,
@@ -251,7 +257,7 @@
     std::vector<QString> plugs =
 	RealTimePluginFactory::getAllPluginIdentifiers();
 
-    QRegExp unitRE("[\\[\\(]([A-Za-z0-9/]+)[\\)\\]]$");
+    static QRegExp unitRE("[\\[\\(]([A-Za-z0-9/]+)[\\)\\]]$");
 
     for (size_t i = 0; i < plugs.size(); ++i) {
         
@@ -273,57 +279,81 @@
 	    continue;
 	}
 	
-        if (descriptor->controlOutputPortCount == 0 ||
-            descriptor->audioInputPortCount == 0) continue;
+//!!!        if (descriptor->controlOutputPortCount == 0 ||
+//            descriptor->audioInputPortCount == 0) continue;
 
-//        std::cout << "TransformFactory::populateRealTimePlugins: plugin " << pluginId.toStdString() << " has " << descriptor->controlOutputPortCount << " output ports" << std::endl;
+        std::cout << "TransformFactory::populateRealTimePlugins: plugin " << pluginId.toStdString() << " has " << descriptor->controlOutputPortCount << " control output ports, " << descriptor->audioOutputPortCount << " audio outputs, " << descriptor->audioInputPortCount << " audio inputs" << std::endl;
 	
 	QString pluginDescription = descriptor->name.c_str();
         QString category = factory->getPluginCategory(pluginId);
+        bool configurable = (descriptor->parameterCount > 0);
 
-	for (size_t j = 0; j < descriptor->controlOutputPortCount; ++j) {
+        if (descriptor->audioInputPortCount > 0) {
 
-	    QString transformName = QString("%1:%2").arg(pluginId).arg(j);
-	    QString userDescription;
-            QString units;
+            for (size_t j = 0; j < descriptor->controlOutputPortCount; ++j) {
 
-	    if (j < descriptor->controlOutputPortNames.size() &&
-                descriptor->controlOutputPortNames[j] != "") {
+                QString transformName = QString("%1:%2").arg(pluginId).arg(j);
+                QString userDescription;
+                QString units;
 
-                QString portName = descriptor->controlOutputPortNames[j].c_str();
+                if (j < descriptor->controlOutputPortNames.size() &&
+                    descriptor->controlOutputPortNames[j] != "") {
 
-		userDescription = tr("%1: %2")
-                    .arg(pluginDescription)
-                    .arg(portName);
+                    QString portName = descriptor->controlOutputPortNames[j].c_str();
 
-                if (unitRE.indexIn(portName) >= 0) {
-                    units = unitRE.cap(1);
+                    userDescription = tr("%1: %2")
+                        .arg(pluginDescription)
+                        .arg(portName);
+
+                    if (unitRE.indexIn(portName) >= 0) {
+                        units = unitRE.cap(1);
+                    }
+
+                } else if (descriptor->controlOutputPortCount > 1) {
+
+                    userDescription = tr("%1: Output %2")
+                        .arg(pluginDescription)
+                        .arg(j + 1);
+
+                } else {
+
+                    userDescription = pluginDescription;
                 }
 
-	    } else if (descriptor->controlOutputPortCount > 1) {
 
-		userDescription = tr("%1: Output %2")
-		    .arg(pluginDescription)
-		    .arg(j + 1);
+                transforms[transformName] = 
+                    TransformDesc(tr("Effects Measurements"),
+                                  category,
+                                  transformName,
+                                  userDescription,
+                                  userDescription,
+                                  descriptor->maker.c_str(),
+                                  units,
+                                  configurable);
+            }
+        }
 
-	    } else {
+        if (!descriptor->isSynth || descriptor->audioInputPortCount > 0) {
 
-                userDescription = pluginDescription;
+            if (descriptor->audioOutputPortCount > 0) {
+
+                QString transformName = QString("%1:A").arg(pluginId);
+                QString type = tr("Effects");
+                if (descriptor->audioInputPortCount == 0) {
+                    type = tr("Generators");
+                }
+
+                transforms[transformName] =
+                    TransformDesc(type,
+                                  category,
+                                  transformName,
+                                  pluginDescription,
+                                  pluginDescription,
+                                  descriptor->maker.c_str(),
+                                  "",
+                                  configurable);
             }
-
-
-            bool configurable = (descriptor->parameterCount > 0);
-
-	    transforms[transformName] = 
-                TransformDesc(tr("Other Plugins"),
-                              category,
-                              transformName,
-                              userDescription,
-                              userDescription,
-                              descriptor->maker.c_str(),
-                              units,
-                              configurable);
-	}
+        }
     }
 }
 
@@ -516,6 +546,7 @@
                                                 context,
                                                 configurationXml,
                                                 getTransformUnits(name),
+                                                output == "A" ? -1 :
                                                 output.toInt());
     } else {
         std::cerr << "TransformFactory::createTransform: Unknown transform \""
--- a/transform/TransformFactory.h	Thu Sep 21 16:43:50 2006 +0000
+++ b/transform/TransformFactory.h	Fri Sep 22 16:12:23 2006 +0000
@@ -41,6 +41,7 @@
     // to be user-readable, for use in menus.
 
     struct TransformDesc {
+
         TransformDesc() { }
 	TransformDesc(QString _type, QString _category,
                       TransformName _name, QString _description,
@@ -50,6 +51,7 @@
             name(_name), description(_description),
             friendlyName(_friendlyName),
             maker(_maker), units(_units), configurable(_configurable) { }
+
         QString type;
         QString category;
 	TransformName name;
@@ -58,6 +60,10 @@
         QString maker;
         QString units;
         bool configurable;
+
+        bool operator<(const TransformDesc &od) const {
+            return (description < od.description);
+        };
     };
     typedef std::vector<TransformDesc> TransformList;
 
@@ -119,7 +125,7 @@
      * If the transform has a prescribed number or range of channel
      * inputs, return true and set minChannels and maxChannels to the
      * minimum and maximum number of channel inputs the transform can
-     * accept.
+     * accept.  Return false if it doesn't care.
      */
     bool getTransformChannelRange(TransformName name,
                                   int &minChannels, int &maxChannels);