changeset 457:ef14acd6d102

* Add beginnings of capability to search plugins that are not yet installed -- lots more work to do here, though
author Chris Cannam
date Tue, 14 Oct 2008 16:36:35 +0000
parents 64e64e304a12
children f60360209e5c
files base/TextMatcher.cpp base/TextMatcher.h base/base.pro data/fileio/FileSource.cpp rdf/PluginRDFDescription.cpp rdf/PluginRDFDescription.h rdf/PluginRDFIndexer.cpp rdf/PluginRDFIndexer.h rdf/SimpleSPARQLQuery.cpp transform/TransformFactory.cpp transform/TransformFactory.h
diffstat 11 files changed, 555 insertions(+), 169 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/TextMatcher.cpp	Tue Oct 14 16:36:35 2008 +0000
@@ -0,0 +1,124 @@
+/* -*- 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 2008 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 "TextMatcher.h"
+
+TextMatcher::TextMatcher()
+{
+}
+
+TextMatcher::~TextMatcher()
+{
+}
+
+void
+TextMatcher::test(Match &match, QStringList keywords, QString text,
+		  QString textType, int score)
+{
+/*
+    if (text.toLower() == keyword.toLower()) {
+        match.score += score * 1.5;
+        match.fragments << tr("%1: <b>%2</b>").arg(textType).arg(text);
+        return;
+    }
+*/
+    int len = text.length();
+    int prevEnd = 0;
+    QString fragment;
+
+    while (1) {
+
+        bool first = (prevEnd == 0);
+        
+        int idx = -1;
+        QString keyword;
+
+        for (int ki = 0; ki < keywords.size(); ++ki) {
+            int midx = text.indexOf(keywords[ki], prevEnd, Qt::CaseInsensitive);
+            if (midx >= 0 && midx < len) {
+                if (midx < idx || idx == -1) {
+                    idx = midx;
+                    keyword = keywords[ki];
+                }
+            }
+        }
+
+        if (idx < 0 || idx >= len) break;
+
+        int klen = keyword.length();
+
+        if (first) {
+            match.score += score;
+        } else {
+            match.score += score / 4;
+        }
+
+        int start = idx;
+        int end = start + klen;
+
+        if (start == 0) match.score += 1;
+        if (end == len) match.score += 1;
+
+        if (start > prevEnd + 14) {
+            QString s = text.right((len - start) + 10);
+            s = XmlExportable::encodeEntities(s.left(10)) + "<b>" +
+                XmlExportable::encodeEntities(s.left(klen + 10).right(klen))
+                + "</b>";
+            fragment += QString("...%1").arg(s);
+        } else {
+            QString s = text.right(len - prevEnd);
+            s = XmlExportable::encodeEntities(s.left(start - prevEnd)) + "<b>" +
+                XmlExportable::encodeEntities(s.left(end - prevEnd).right(klen))
+                + "</b>";
+            fragment += s;
+        }
+
+        prevEnd = end;
+    }
+
+    if (prevEnd > 0 && prevEnd < len) {
+        int n = len - prevEnd;
+        fragment +=
+            XmlExportable::encodeEntities(text.right(n).left(n < 8 ? n : 8));
+    }
+
+    if (fragment != "") {
+        match.fragments[textType] = fragment;
+    }
+}
+
+bool
+TextMatcher::Match::operator<(const Match &m) const
+{
+    if (score != m.score) {
+        return score < m.score;
+    }
+    if (key != m.key) {
+        return key < m.key;
+    }
+    if (fragments.size() != m.fragments.size()) {
+        return fragments.size() < m.fragments.size();
+    }
+
+    for (FragmentMap::const_iterator
+             i = fragments.begin(),
+             j = m.fragments.begin();
+         i != fragments.end(); ++i, ++j) {
+        if (i->first != j->first) return i->first < j->first;
+        if (i->second != j->second) return i->second < j->second;
+    }
+
+    return false;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/TextMatcher.h	Tue Oct 14 16:36:35 2008 +0000
@@ -0,0 +1,56 @@
+/* -*- 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 2008 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.
+*/
+
+#ifndef _TEXT_MATCHER_H_
+#define _TEXT_MATCHER_H_
+
+#include <QString>
+#include <QStringList>
+#include "XmlExportable.h"
+
+#include <map>
+
+/// A rather eccentric interface for matching texts in differently-scored fields
+
+class TextMatcher
+{
+public:
+    TextMatcher();
+    virtual ~TextMatcher();
+    
+    struct Match
+    {
+        QString key; // This field is not used by TextMatcher
+        int score;
+        typedef std::map<QString, QString> FragmentMap; // text type -> fragment
+        FragmentMap fragments;
+
+        Match() : score(0) { }
+        Match(const Match &m) :
+            key(m.key), score(m.score), fragments(m.fragments) { }
+
+        bool operator<(const Match &m) const; // sort by score first
+    };
+
+    void test(Match &match, // existing match record to be augmented
+              QStringList keywords, // search terms
+              QString text, // to search within
+              QString textType, // key to use for fragment map
+              int score); // relative weight for hits within this text type
+
+};
+
+
+#endif
--- a/base/base.pro	Mon Oct 13 13:53:05 2008 +0000
+++ b/base/base.pro	Tue Oct 14 16:36:35 2008 +0000
@@ -40,6 +40,7 @@
            Serialiser.h \
            StorageAdviser.h \
            TempDirectory.h \
+           TextMatcher.h \
            Thread.h \
            UnitDatabase.h \
            ViewManagerBase.h \
@@ -67,6 +68,7 @@
            Serialiser.cpp \
            StorageAdviser.cpp \
            TempDirectory.cpp \
+           TextMatcher.cpp \
            Thread.cpp \
            UnitDatabase.cpp \
            ViewManagerBase.cpp \
--- a/data/fileio/FileSource.cpp	Mon Oct 13 13:53:05 2008 +0000
+++ b/data/fileio/FileSource.cpp	Tue Oct 14 16:36:35 2008 +0000
@@ -743,7 +743,9 @@
     QString filepart = m_url.path().section('/', -1, -1,
                                             QString::SectionSkipEmpty);
 
-    QString extension = filepart.section('.', -1);
+    QString extension = "";
+    if (filepart.contains('.')) extension = filepart.section('.', -1);
+
     QString base = filepart;
     if (extension != "") {
         base = base.left(base.length() - extension.length() - 1);
--- a/rdf/PluginRDFDescription.cpp	Mon Oct 13 13:53:05 2008 +0000
+++ b/rdf/PluginRDFDescription.cpp	Tue Oct 14 16:36:35 2008 +0000
@@ -18,6 +18,10 @@
 #include "PluginRDFIndexer.h"
 #include "SimpleSPARQLQuery.h"
 
+#include "data/fileio/FileSource.h"
+
+#include "base/Profiler.h"
+
 #include "plugin/PluginIdentifier.h"
 
 #include <iostream>
@@ -25,6 +29,7 @@
 using std::endl;
 
 PluginRDFDescription::PluginRDFDescription(QString pluginId) :
+    m_source(0),
     m_pluginId(pluginId),
     m_haveDescription(false)
 {
@@ -45,6 +50,7 @@
 
 PluginRDFDescription::~PluginRDFDescription()
 {
+    delete m_source;
 }
 
 bool
@@ -53,6 +59,44 @@
     return m_haveDescription;
 }
 
+QString
+PluginRDFDescription::getPluginName() const
+{
+    return m_pluginName;
+}
+
+QString
+PluginRDFDescription::getPluginDescription() const
+{
+    return m_pluginDescription;
+}
+
+QString
+PluginRDFDescription::getPluginMaker() const
+{
+    return m_pluginMaker;
+}
+
+QStringList
+PluginRDFDescription::getOutputIds() const
+{
+    QStringList ids;
+    for (OutputDispositionMap::const_iterator i = m_outputDispositions.begin();
+         i != m_outputDispositions.end(); ++i) {
+        ids.push_back(i->first);
+    }
+    return ids;
+}
+
+QString
+PluginRDFDescription::getOutputName(QString outputId) const
+{
+    if (m_outputNames.find(outputId) == m_outputNames.end()) {
+        return "";
+    } 
+    return m_outputNames.find(outputId)->second;
+}
+
 PluginRDFDescription::OutputDisposition
 PluginRDFDescription::getOutputDisposition(QString outputId) const
 {
@@ -104,9 +148,95 @@
 bool
 PluginRDFDescription::indexURL(QString url) 
 {
+    Profiler profiler("PluginRDFDescription::indexURL");
+
     QString type, soname, label;
     PluginIdentifier::parseIdentifier(m_pluginId, type, soname, label);
 
+    bool success = true;
+
+    QString local = url;
+
+    if (FileSource::isRemote(url) &&
+        FileSource::canHandleScheme(url)) {
+
+        m_source = new FileSource(url);
+        if (!m_source->isAvailable()) {
+            delete m_source;
+            m_source = 0;
+            return false;
+        }
+        m_source->waitForData();
+        local = QUrl::fromLocalFile(m_source->getLocalFilename()).toString();
+    }
+    
+    if (!indexMetadata(local, label)) success = false;
+    if (!indexOutputs(local, label)) success = false;
+
+    return success;
+}
+
+bool
+PluginRDFDescription::indexMetadata(QString url, QString label)
+{
+    Profiler profiler("PluginRDFDescription::indexMetadata");
+
+    QString queryTemplate =
+        QString(
+            " PREFIX vamp: <http://purl.org/ontology/vamp/> "
+            " PREFIX foaf: <http://xmlns.com/foaf/0.1/> "
+            " PREFIX dc: <http://purl.org/dc/elements/1.1/> "
+            " SELECT ?%4 FROM <%1> "
+            " WHERE { "
+            "   ?plugin a vamp:Plugin ; "
+            "           vamp:identifier \"%2\" ; "
+            "           %3 ?%4 . "
+            " }")
+        .arg(url)
+        .arg(label);
+
+    SimpleSPARQLQuery::Value v;
+
+    v = SimpleSPARQLQuery::singleResultQuery
+        (queryTemplate.arg("vamp:name").arg("name"), "name");
+    
+    if (v.type == SimpleSPARQLQuery::LiteralValue && v.value != "") {
+        m_pluginName = v.value;
+    }
+
+    v = SimpleSPARQLQuery::singleResultQuery
+        (queryTemplate.arg("dc:description").arg("description"), "description");
+    
+    if (v.type == SimpleSPARQLQuery::LiteralValue && v.value != "") {
+        m_pluginDescription = v.value;
+    }
+
+    v = SimpleSPARQLQuery::singleResultQuery
+        (QString(
+            " PREFIX vamp: <http://purl.org/ontology/vamp/> "
+            " PREFIX foaf: <http://xmlns.com/foaf/0.1/> "
+            " SELECT ?name FROM <%1> "
+            " WHERE { "
+            "   ?plugin a vamp:Plugin ; "
+            "           vamp:identifier \"%2\" ; "
+            "           foaf:maker ?maker . "
+            "   ?maker foaf:name ?name . "
+            " }")
+         .arg(url)
+         .arg(label), "name");
+    
+    if (v.type == SimpleSPARQLQuery::LiteralValue && v.value != "") {
+        m_pluginMaker = v.value;
+    }
+
+    return true;
+}
+
+bool
+PluginRDFDescription::indexOutputs(QString url, QString label)
+{
+    Profiler profiler("PluginRDFDescription::indexOutputs");
+
     SimpleSPARQLQuery query
         (QString
          (
@@ -166,6 +296,8 @@
             m_outputDispositions[outputId] = OutputSparse;
         } else if (outputType.contains("TrackLevelOutput")) {
             m_outputDispositions[outputId] = OutputTrackLevel;
+        } else {
+            m_outputDispositions[outputId] = OutputDispositionUnknown;
         }
             
         if (results[i]["unit"].type == SimpleSPARQLQuery::LiteralValue) {
@@ -177,14 +309,25 @@
             }
         }
 
+        SimpleSPARQLQuery::Value v;
+
+        v = SimpleSPARQLQuery::singleResultQuery
+            (QString(" PREFIX vamp: <http://purl.org/ontology/vamp/> "
+                     " PREFIX dc: <http://purl.org/dc/elements/1.1/> "
+                     " SELECT ?title FROM <%1> "
+                     " WHERE { <%2> dc:title ?title } ")
+             .arg(url).arg(outputUri), "title");
+
+        if (v.type == SimpleSPARQLQuery::LiteralValue && v.value != "") {
+            m_outputNames[outputId] = v.value;
+        }
+
         QString queryTemplate = 
             QString(" PREFIX vamp: <http://purl.org/ontology/vamp/> "
                     " SELECT ?%3 FROM <%1> "
                     " WHERE { <%2> vamp:computes_%3 ?%3 } ")
             .arg(url).arg(outputUri);
 
-        SimpleSPARQLQuery::Value v;
-
         v = SimpleSPARQLQuery::singleResultQuery
             (queryTemplate.arg("event_type"), "event_type");
 
--- a/rdf/PluginRDFDescription.h	Mon Oct 13 13:53:05 2008 +0000
+++ b/rdf/PluginRDFDescription.h	Tue Oct 14 16:36:35 2008 +0000
@@ -17,6 +17,7 @@
 #define _PLUGIN_RDF_DESCRIPTION_H_
 
 #include <QString>
+#include <QStringList>
 #include <map>
 
 class FileSource;
@@ -37,6 +38,13 @@
     };
 
     bool haveDescription() const;
+
+    QString getPluginName() const;
+    QString getPluginDescription() const;
+    QString getPluginMaker() const;
+
+    QStringList getOutputIds() const;
+    QString getOutputName(QString outputId) const;
     OutputDisposition getOutputDisposition(QString outputId) const;
     QString getOutputEventTypeURI(QString outputId) const;
     QString getOutputFeatureAttributeURI(QString outputId) const;
@@ -47,14 +55,21 @@
     typedef std::map<QString, OutputDisposition> OutputDispositionMap;
     typedef std::map<QString, QString> OutputStringMap;
 
+    FileSource *m_source;
     QString m_pluginId;
     bool m_haveDescription;
+    QString m_pluginName;
+    QString m_pluginDescription;
+    QString m_pluginMaker;
+    OutputStringMap m_outputNames;
     OutputDispositionMap m_outputDispositions;
     OutputStringMap m_outputEventTypeURIMap;
     OutputStringMap m_outputFeatureAttributeURIMap;
     OutputStringMap m_outputSignalTypeURIMap;
     OutputStringMap m_outputUnitMap;
     bool indexURL(QString url);
+    bool indexMetadata(QString url, QString label);
+    bool indexOutputs(QString url, QString label);
 };
 
 #endif
--- a/rdf/PluginRDFIndexer.cpp	Mon Oct 13 13:53:05 2008 +0000
+++ b/rdf/PluginRDFIndexer.cpp	Tue Oct 14 16:36:35 2008 +0000
@@ -20,6 +20,8 @@
 #include "data/fileio/FileSource.h"
 #include "plugin/PluginIdentifier.h"
 
+#include "base/Profiler.h"
+
 #include <vamp-sdk/PluginHostAdapter.h>
 
 #include <QFileInfo>
@@ -93,9 +95,9 @@
 
 PluginRDFIndexer::~PluginRDFIndexer()
 {
-    while (!m_cache.empty()) {
-        delete *m_cache.begin();
-        m_cache.erase(m_cache.begin());
+    while (!m_sources.empty()) {
+        delete *m_sources.begin();
+        m_sources.erase(m_sources.begin());
     }
 }
 
@@ -122,13 +124,7 @@
 
         QString baseUrl = QUrl(uri).toString(QUrl::RemoveFragment);
 
-        FileSource source(baseUrl);
-        if (source.isAvailable()) {
-            source.waitForData();
-            if (indexFile(source.getLocalFilename())) {
-                m_cache.insert(new FileSource(source));
-            }
-        }
+        indexURL(baseUrl);
 
         if (m_uriToIdMap.find(uri) == m_uriToIdMap.end()) {
             m_uriToIdMap[uri] = "";
@@ -175,6 +171,23 @@
 bool
 PluginRDFIndexer::indexURL(QString urlString)
 {
+    Profiler profiler("PluginRDFIndexer::indexURL");
+
+    QString localString = urlString;
+
+    if (FileSource::isRemote(urlString) &&
+        FileSource::canHandleScheme(urlString)) {
+
+        FileSource *source = new FileSource(urlString);
+        if (!source->isAvailable()) {
+            delete source;
+            return false;
+        }
+        source->waitForData();
+        localString = QUrl::fromLocalFile(source->getLocalFilename()).toString();
+        m_sources.insert(source);
+    }
+
 //    cerr << "PluginRDFIndexer::indexURL: url = <" << urlString.toStdString() << ">" << endl;
 
     SimpleSPARQLQuery query
@@ -206,7 +219,7 @@
              "   } "
              " } "
              )
-         .arg(urlString));
+         .arg(localString));
 
     SimpleSPARQLQuery::ResultList results = query.execute();
 
--- a/rdf/PluginRDFIndexer.h	Mon Oct 13 13:53:05 2008 +0000
+++ b/rdf/PluginRDFIndexer.h	Tue Oct 14 16:36:35 2008 +0000
@@ -41,12 +41,12 @@
 
 protected:
     PluginRDFIndexer();
+    std::set<FileSource *> m_sources;
     typedef std::map<QString, QString> StringMap;
     StringMap m_uriToIdMap;
     StringMap m_idToUriMap;
     StringMap m_idToDescriptionMap;
     bool indexFile(QString path);
-    std::set<FileSource *> m_cache;
     static PluginRDFIndexer *m_instance;
 };
 
--- a/rdf/SimpleSPARQLQuery.cpp	Mon Oct 13 13:53:05 2008 +0000
+++ b/rdf/SimpleSPARQLQuery.cpp	Tue Oct 14 16:36:35 2008 +0000
@@ -121,7 +121,7 @@
     m_reporter(0),
     m_cancelled(false)
 {
-    std::cerr << "SimpleSPARQLQuery::Impl: Query is: \"" << query.toStdString() << "\"" << std::endl;
+//    std::cerr << "SimpleSPARQLQuery::Impl: Query is: \"" << query.toStdString() << "\"" << std::endl;
 }
 
 SimpleSPARQLQuery::Impl::~Impl()
--- a/transform/TransformFactory.cpp	Mon Oct 13 13:53:05 2008 +0000
+++ b/transform/TransformFactory.cpp	Tue Oct 14 16:36:35 2008 +0000
@@ -24,6 +24,9 @@
 #include "vamp-sdk/PluginHostAdapter.h"
 #include "vamp-sdk/hostext/PluginWrapper.h"
 
+#include "rdf/PluginRDFIndexer.h"
+#include "rdf/PluginRDFDescription.h"
+
 #include "base/XmlExportable.h"
 
 #include <iostream>
@@ -44,6 +47,12 @@
     return m_instance;
 }
 
+TransformFactory::TransformFactory() :
+    m_transformsPopulated(false),
+    m_uninstalledTransformsPopulated(false)
+{
+}
+
 TransformFactory::~TransformFactory()
 {
 }
@@ -51,7 +60,7 @@
 TransformList
 TransformFactory::getAllTransformDescriptions()
 {
-    if (m_transforms.empty()) populateTransforms();
+    if (!m_transformsPopulated) populateTransforms();
 
     std::set<TransformDescription> dset;
     for (TransformDescriptionMap::const_iterator i = m_transforms.begin();
@@ -73,7 +82,7 @@
 TransformDescription
 TransformFactory::getTransformDescription(TransformId id)
 {
-    if (m_transforms.empty()) populateTransforms();
+    if (!m_transformsPopulated) populateTransforms();
 
     if (m_transforms.find(id) == m_transforms.end()) {
         return TransformDescription();
@@ -82,10 +91,60 @@
     return m_transforms[id];
 }
 
+TransformList
+TransformFactory::getUninstalledTransformDescriptions()
+{
+    if (!m_uninstalledTransformsPopulated) populateUninstalledTransforms();
+    
+    std::set<TransformDescription> dset;
+    for (TransformDescriptionMap::const_iterator i = m_uninstalledTransforms.begin();
+	 i != m_uninstalledTransforms.end(); ++i) {
+//        cerr << "inserting transform into set: id = " << i->second.identifier.toStdString() << endl;
+	dset.insert(i->second);
+    }
+
+    TransformList list;
+    for (std::set<TransformDescription>::const_iterator i = dset.begin();
+	 i != dset.end(); ++i) {
+//        cerr << "inserting transform into list: id = " << i->identifier.toStdString() << endl;
+	list.push_back(*i);
+    }
+
+    return list;
+}
+
+TransformDescription
+TransformFactory::getUninstalledTransformDescription(TransformId id)
+{
+    if (!m_uninstalledTransformsPopulated) populateUninstalledTransforms();
+
+    if (m_uninstalledTransforms.find(id) == m_uninstalledTransforms.end()) {
+        return TransformDescription();
+    }
+
+    return m_uninstalledTransforms[id];
+}
+
+TransformFactory::TransformInstallStatus
+TransformFactory::getTransformInstallStatus(TransformId id)
+{
+    if (!m_transformsPopulated) populateTransforms();
+    if (!m_uninstalledTransformsPopulated) populateUninstalledTransforms();
+
+    if (m_transforms.find(id) != m_transforms.end()) {
+        return TransformInstalled;
+    }
+    if (m_uninstalledTransforms.find(id) != m_uninstalledTransforms.end()) {
+        return TransformNotInstalled;
+    }
+    return TransformUnknown;
+}
+    
+
 std::vector<QString>
 TransformFactory::getAllTransformTypes()
 {
-    if (m_transforms.empty()) populateTransforms();
+    if (!m_transformsPopulated) populateTransforms();
 
     std::set<QString> types;
     for (TransformDescriptionMap::const_iterator i = m_transforms.begin();
@@ -104,7 +163,7 @@
 std::vector<QString>
 TransformFactory::getTransformCategories(QString transformType)
 {
-    if (m_transforms.empty()) populateTransforms();
+    if (!m_transformsPopulated) populateTransforms();
 
     std::set<QString> categories;
     for (TransformDescriptionMap::const_iterator i = m_transforms.begin();
@@ -131,7 +190,7 @@
 std::vector<QString>
 TransformFactory::getTransformMakers(QString transformType)
 {
-    if (m_transforms.empty()) populateTransforms();
+    if (!m_transformsPopulated) populateTransforms();
 
     std::set<QString> makers;
     for (TransformDescriptionMap::const_iterator i = m_transforms.begin();
@@ -216,6 +275,8 @@
 
 	m_transforms[identifier] = desc;
     }	    
+
+    m_transformsPopulated = true;
 }
 
 void
@@ -448,6 +509,73 @@
     }
 }
 
+void
+TransformFactory::populateUninstalledTransforms()
+{
+    if (!m_uninstalledTransforms.empty()) return;
+    if (m_transforms.empty()) populateTransforms();
+
+    //!!! This will be amazingly slow
+
+    QStringList ids = PluginRDFIndexer::getInstance()->getIndexedPluginIds();
+    
+    for (QStringList::const_iterator i = ids.begin(); i != ids.end(); ++i) {
+        
+        PluginRDFDescription desc(*i);
+
+        QString name = desc.getPluginName();
+//        if (name == "") {
+//            std::cerr << "TransformFactory::populateUninstalledTransforms: "
+//                      << "No name available for plugin " << i->toStdString()
+//                      << ", skipping" << std::endl;
+//            continue;
+//        }
+
+        QString description = desc.getPluginDescription();
+        QString maker = desc.getPluginMaker();
+
+        QStringList oids = desc.getOutputIds();
+
+        for (QStringList::const_iterator j = oids.begin(); j != oids.end(); ++j) {
+
+            TransformId tid = Transform::getIdentifierForPluginOutput(*i, *j);
+            
+            if (m_transforms.find(tid) != m_transforms.end()) {
+                std::cerr << "TransformFactory::populateUninstalledTransforms: "
+                          << tid.toStdString() << " is installed, skipping" << std::endl;
+                continue;
+            }
+
+            std::cerr << "TransformFactory::populateUninstalledTransforms: "
+                      << "adding " << tid.toStdString() << std::endl;
+
+            QString oname = desc.getOutputName(*j);
+            if (oname == "") oname = *j;
+            
+            TransformDescription td;
+            td.type = tr("Analysis"); //!!! should be enum or something
+            td.category = "";
+            td.identifier = tid;
+
+            if (oids.size() == 1) {
+                td.name = name;
+            } else if (name != "") {
+                td.name = tr("%1: %2").arg(name).arg(oname);
+            }
+
+            td.friendlyName = name; //!!!???
+            td.description = description;
+            td.longDescription = ""; //!!!
+            td.maker = maker;
+            td.units = "";
+            td.configurable = false;
+
+            m_uninstalledTransforms[tid] = td;
+        }
+    }
+
+    m_uninstalledTransformsPopulated = true;
+}
 
 Transform
 TransformFactory::getDefaultTransformFor(TransformId id, size_t rate)
@@ -787,27 +915,6 @@
     setParametersFromPlugin(t, plugin);
     delete plugin;
 }
-/*
-TransformFactory::SearchResults
-TransformFactory::search(QStringList keywords)
-{
-    SearchResults results;
-    SearchResults partial;
-    for (int i = 0; i < keywords.size(); ++i) {
-        partial = search(keywords[i]);
-        for (SearchResults::const_iterator j = partial.begin();
-             j != partial.end(); ++j) {
-            if (results.find(j->first) == results.end()) {
-                results[j->first] = j->second;
-            } else {
-                results[j->first].score += j->second.score;
-                results[j->first].fragments << j->second.fragments;
-            }
-        }
-    }
-    return results;
-}
-*/
 
 TransformFactory::SearchResults
 TransformFactory::search(QString keyword)
@@ -828,21 +935,42 @@
     }
 
     SearchResults results;
+    TextMatcher matcher;
 
     for (TransformDescriptionMap::const_iterator i = m_transforms.begin();
          i != m_transforms.end(); ++i) {
 
-        Match match;
+        TextMatcher::Match match;
 
-        match.transform = i->first;
+        match.key = i->first;
         
-        searchTest(match, keywords, i->second.type, tr("Plugin type"), 10);
-        searchTest(match, keywords, i->second.category, tr("Category"), 20);
-        searchTest(match, keywords, i->second.identifier, tr("System Identifier"), 5);
-        searchTest(match, keywords, i->second.name, tr("Name"), 30);
-        searchTest(match, keywords, i->second.description, tr("Description"), 20);
-        searchTest(match, keywords, i->second.maker, tr("Maker"), 10);
-        searchTest(match, keywords, i->second.units, tr("Units"), 10);
+        matcher.test(match, keywords, i->second.type, tr("Plugin type"), 5);
+        matcher.test(match, keywords, i->second.category, tr("Category"), 20);
+        matcher.test(match, keywords, i->second.identifier, tr("System Identifier"), 6);
+        matcher.test(match, keywords, i->second.name, tr("Name"), 30);
+        matcher.test(match, keywords, i->second.description, tr("Description"), 20);
+        matcher.test(match, keywords, i->second.maker, tr("Maker"), 10);
+        matcher.test(match, keywords, i->second.units, tr("Units"), 10);
+
+        if (match.score > 0) results[i->first] = match;
+    }
+
+    if (m_uninstalledTransforms.empty()) populateUninstalledTransforms();
+
+    for (TransformDescriptionMap::const_iterator i = m_uninstalledTransforms.begin();
+         i != m_uninstalledTransforms.end(); ++i) {
+
+        TextMatcher::Match match;
+
+        match.key = i->first;
+        
+        matcher.test(match, keywords, i->second.type, tr("Plugin type"), 2);
+        matcher.test(match, keywords, i->second.category, tr("Category"), 10);
+        matcher.test(match, keywords, i->second.identifier, tr("System Identifier"), 3);
+        matcher.test(match, keywords, i->second.name, tr("Name"), 15);
+        matcher.test(match, keywords, i->second.description, tr("Description"), 10);
+        matcher.test(match, keywords, i->second.maker, tr("Maker"), 5);
+        matcher.test(match, keywords, i->second.units, tr("Units"), 5);
 
         if (match.score > 0) results[i->first] = match;
     }
@@ -850,103 +978,3 @@
     return results;
 }
 
-void
-TransformFactory::searchTest(Match &match, QStringList keywords, QString text,
-                             QString textType, int score)
-{
-/*
-    if (text.toLower() == keyword.toLower()) {
-        match.score += score * 1.5;
-        match.fragments << tr("%1: <b>%2</b>").arg(textType).arg(text);
-        return;
-    }
-*/
-    int len = text.length();
-    int prevEnd = 0;
-    QString fragment;
-
-    while (1) {
-
-        bool first = (prevEnd == 0);
-        
-        int idx = -1;
-        QString keyword;
-
-        for (int ki = 0; ki < keywords.size(); ++ki) {
-            int midx = text.indexOf(keywords[ki], prevEnd, Qt::CaseInsensitive);
-            if (midx >= 0 && midx < len) {
-                if (midx < idx || idx == -1) {
-                    idx = midx;
-                    keyword = keywords[ki];
-                }
-            }
-        }
-
-        if (idx < 0 || idx >= len) break;
-
-        int klen = keyword.length();
-
-        if (first) {
-            match.score += score;
-        } else {
-            match.score += score / 4;
-        }
-
-        int start = idx;
-        int end = start + klen;
-
-        if (start == 0) match.score += 1;
-        if (end == len) match.score += 1;
-
-        if (start > prevEnd + 14) {
-            QString s = text.right((len - start) + 10);
-            s = XmlExportable::encodeEntities(s.left(10)) + "<b>" +
-                XmlExportable::encodeEntities(s.left(klen + 10).right(klen))
-                + "</b>";
-            fragment += tr("...%1").arg(s);
-        } else {
-            QString s = text.right(len - prevEnd);
-            s = XmlExportable::encodeEntities(s.left(start - prevEnd)) + "<b>" +
-                XmlExportable::encodeEntities(s.left(end - prevEnd).right(klen))
-                + "</b>";
-            fragment += s;
-        }
-
-        prevEnd = end;
-    }
-
-    if (prevEnd > 0 && prevEnd < len) {
-        int n = len - prevEnd;
-        fragment +=
-            XmlExportable::encodeEntities(text.right(n).left(n < 8 ? n : 8));
-    }
-
-    if (fragment != "") {
-        match.fragments[textType] = fragment;
-    }
-}
-
-bool
-TransformFactory::Match::operator<(const Match &m) const
-{
-    if (score != m.score) {
-        return score < m.score;
-    }
-    if (transform != m.transform) {
-        return transform < m.transform;
-    }
-    if (fragments.size() != m.fragments.size()) {
-        return fragments.size() < m.fragments.size();
-    }
-
-    for (FragmentMap::const_iterator
-             i = fragments.begin(),
-             j = m.fragments.begin();
-         i != fragments.end(); ++i, ++j) {
-        if (i->first != j->first) return i->first < j->first;
-        if (i->second != j->second) return i->second < j->second;
-    }
-
-    return false;
-}
-
--- a/transform/TransformFactory.h	Mon Oct 13 13:53:05 2008 +0000
+++ b/transform/TransformFactory.h	Tue Oct 14 16:36:35 2008 +0000
@@ -18,6 +18,8 @@
 
 #include "TransformDescription.h"
 
+#include "base/TextMatcher.h"
+
 #include <vamp-sdk/Plugin.h>
 
 #include <QObject>
@@ -31,6 +33,7 @@
     Q_OBJECT
 
 public:
+    TransformFactory();
     virtual ~TransformFactory();
 
     static TransformFactory *getInstance();
@@ -38,25 +41,22 @@
     TransformList getAllTransformDescriptions();
     TransformDescription getTransformDescription(TransformId id);
 
+    TransformList getUninstalledTransformDescriptions();
+    TransformDescription getUninstalledTransformDescription(TransformId id);
+    
+    typedef enum {
+        TransformUnknown,
+        TransformInstalled,
+        TransformNotInstalled
+    } TransformInstallStatus;
+
+    TransformInstallStatus getTransformInstallStatus(TransformId id);
+
     std::vector<QString> getAllTransformTypes();
     std::vector<QString> getTransformCategories(QString transformType);
     std::vector<QString> getTransformMakers(QString transformType);
 
-    struct Match
-    {
-        TransformId transform;
-        int score;
-        typedef std::map<QString, QString> FragmentMap;
-        FragmentMap fragments;
-
-        Match() : score(0) { }
-        Match(const Match &m) :
-            transform(m.transform), score(m.score), fragments(m.fragments) { }
-
-        bool operator<(const Match &m) const; // sort by score first
-    };
-
-    typedef std::map<TransformId, Match> SearchResults;
+    typedef std::map<TransformId, TextMatcher::Match> SearchResults;
     SearchResults search(QString keyword);
     SearchResults search(QStringList keywords);
     
@@ -178,15 +178,18 @@
 
 protected:
     typedef std::map<TransformId, TransformDescription> TransformDescriptionMap;
+
     TransformDescriptionMap m_transforms;
+    bool m_transformsPopulated;
+
+    TransformDescriptionMap m_uninstalledTransforms;
+    bool m_uninstalledTransformsPopulated;
 
     void populateTransforms();
+    void populateUninstalledTransforms();
     void populateFeatureExtractionPlugins(TransformDescriptionMap &);
     void populateRealTimePlugins(TransformDescriptionMap &);
 
-    void searchTest(Match &match, QStringList keywords, QString text,
-                    QString textType, int score);
-
     Vamp::PluginBase *instantiateDefaultPluginFor(TransformId id, size_t rate);
 
     static TransformFactory *m_instance;