Chris@416: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
Chris@416: 
Chris@416: /*
Chris@416:     Sonic Visualiser
Chris@416:     An audio file viewer and annotation editor.
Chris@416:     Centre for Digital Music, Queen Mary, University of London.
Chris@416:     This file copyright 2008 QMUL.
Chris@416:     
Chris@416:     This program is free software; you can redistribute it and/or
Chris@416:     modify it under the terms of the GNU General Public License as
Chris@416:     published by the Free Software Foundation; either version 2 of the
Chris@416:     License, or (at your option) any later version.  See the file
Chris@416:     COPYING included with this distribution for more information.
Chris@416: */
Chris@416: 
Chris@416: #include "TransformFinder.h"
Chris@416: 
Chris@424: #include "base/XmlExportable.h"
Chris@416: #include "transform/TransformFactory.h"
Chris@424: #include "SelectableLabel.h"
Chris@416: 
Chris@421: #include <QVBoxLayout>
Chris@416: #include <QGridLayout>
Chris@416: #include <QLineEdit>
Chris@416: #include <QLabel>
Chris@416: #include <QDialogButtonBox>
Chris@416: #include <QScrollArea>
Chris@421: #include <QApplication>
Chris@423: #include <QDesktopWidget>
Chris@424: #include <QTimer>
Chris@426: #include <QAction>
Chris@421: 
Chris@416: TransformFinder::TransformFinder(QWidget *parent) :
Chris@417:     QDialog(parent),
Chris@417:     m_resultsFrame(0),
Chris@417:     m_resultsLayout(0)
Chris@416: {
Chris@416:     setWindowTitle(tr("Find a Transform"));
Chris@416:     
Chris@416:     QGridLayout *mainGrid = new QGridLayout;
Chris@421:     mainGrid->setVerticalSpacing(0);
Chris@416:     setLayout(mainGrid);
Chris@416: 
Chris@416:     mainGrid->addWidget(new QLabel(tr("Find:")), 0, 0);
Chris@416:     
Chris@416:     QLineEdit *searchField = new QLineEdit;
Chris@416:     mainGrid->addWidget(searchField, 0, 1);
Chris@416:     connect(searchField, SIGNAL(textChanged(const QString &)),
Chris@416:             this, SLOT(searchTextChanged(const QString &)));
Chris@416: 
Chris@450: //    m_infoLabel = new QLabel(tr("Type in this box to search descriptions of available and known transforms"));
Chris@450:     m_infoLabel = new QLabel;
Chris@448:     mainGrid->addWidget(m_infoLabel, 1, 1);
Chris@448: 
Chris@416:     m_resultsScroll = new QScrollArea;
Chris@448:     mainGrid->addWidget(m_resultsScroll, 2, 0, 1, 2);
Chris@448:     mainGrid->setRowStretch(2, 10);
Chris@416: 
Chris@416:     QDialogButtonBox *bb = new QDialogButtonBox(QDialogButtonBox::Ok |
Chris@416:                                                 QDialogButtonBox::Cancel);
Chris@448:     mainGrid->addWidget(bb, 3, 0, 1, 2);
Chris@416:     connect(bb, SIGNAL(accepted()), this, SLOT(accept()));
Chris@416:     connect(bb, SIGNAL(rejected()), this, SLOT(reject()));
Chris@448: 
Chris@448:     m_resultsFrame = new QWidget;
Chris@448:     QPalette palette = m_resultsFrame->palette();
Chris@448:     palette.setColor(QPalette::Window, palette.color(QPalette::Base));
Chris@448:     m_resultsFrame->setPalette(palette);
Chris@448:     m_resultsScroll->setPalette(palette);
Chris@448:     m_resultsLayout = new QVBoxLayout;
Chris@448:     m_resultsLayout->setSpacing(0);
Chris@448:     m_resultsLayout->setContentsMargins(0, 0, 0, 0);
Chris@448:     m_resultsFrame->setLayout(m_resultsLayout);
Chris@448:     m_resultsScroll->setWidget(m_resultsFrame);
Chris@448:     m_resultsFrame->show();
Chris@448: 
Chris@448:     m_noResultsLabel = new QLabel(tr("<br>&nbsp;&nbsp;No results found"));
Chris@448:     m_resultsLayout->addWidget(m_noResultsLabel);
Chris@448:     m_noResultsLabel->hide();
Chris@448: 
Chris@448:     m_beforeSearchLabel = new QLabel;
Chris@448:     m_resultsLayout->addWidget(m_beforeSearchLabel);
Chris@448:     m_beforeSearchLabel->hide();
Chris@416: 
Chris@426:     QAction *up = new QAction(tr("Up"), this);
Chris@426:     up->setShortcut(tr("Up"));
Chris@426:     connect(up, SIGNAL(triggered()), this, SLOT(up()));
Chris@426:     addAction(up);
Chris@426: 
Chris@426:     QAction *down = new QAction(tr("Down"), this);
Chris@426:     down->setShortcut(tr("Down"));
Chris@426:     connect(down, SIGNAL(triggered()), this, SLOT(down()));
Chris@426:     addAction(down);
Chris@426: 
Chris@423:     QDesktopWidget *desktop = QApplication::desktop();
Chris@423:     QRect available = desktop->availableGeometry();
Chris@423: 
Chris@423:     int width = available.width() / 2;
Chris@423:     int height = available.height() / 2;
Chris@423:     if (height < 450) {
Chris@423:         if (available.height() > 500) height = 450;
Chris@423:     }
Chris@423:     if (width < 600) {
Chris@423:         if (available.width() > 650) width = 600;
Chris@423:     }
Chris@423: 
Chris@423:     resize(width, height);
Chris@423:     raise();
Chris@424: 
Chris@450:     setupBeforeSearchLabel();
Chris@450: 
Chris@424:     m_upToDateCount = 0;
Chris@424:     m_timer = new QTimer(this);
Chris@424:     connect(m_timer, SIGNAL(timeout()), this, SLOT(timeout()));
Chris@425:     m_timer->start(30);
Chris@416: }
Chris@416: 
Chris@416: TransformFinder::~TransformFinder()
Chris@416: {
Chris@416: }
Chris@416: 
Chris@416: void
Chris@450: TransformFinder::setupBeforeSearchLabel()
Chris@450: {
Chris@450:     bool haveInstalled =
Chris@450:         TransformFactory::getInstance()->haveInstalledTransforms();
Chris@450:     bool haveUninstalled =
Chris@450:         TransformFactory::getInstance()->haveUninstalledTransforms();
Chris@450: 
Chris@450:     m_beforeSearchLabel->setWordWrap(true);
Chris@450:     m_beforeSearchLabel->setOpenExternalLinks(true);
Chris@450:     m_beforeSearchLabel->setTextInteractionFlags(Qt::LinksAccessibleByMouse);
Chris@450:     m_beforeSearchLabel->setMargin(12);
Chris@450:     m_beforeSearchLabel->setFixedWidth(this->width() - 40);
Chris@450: 
Chris@450:     QString base =
Chris@450:         tr("<p>Type some text into the search box to search the descriptions of:<ul><li>All currently installed <a href=\"http://www.vamp-plugins.org/\">Vamp</a> audio feature extraction plugins</li><li>All currently installed <a href=\"http://www.ladspa.org/\">LADSPA</a> audio effects plugins</li><li>Vamp plugins that are not currently installed but that have descriptions published via the semantic web</li></ul>");
Chris@450: 
Chris@450:     QString nopull =
Chris@450:         tr("<b>Unable to retrieve published descriptions from network!</b>");
Chris@450: 
Chris@450:     QString noinst =
Chris@450:         tr("<b>No plugins are currently installed!</b>");
Chris@450: 
Chris@450:     if (haveInstalled) {
Chris@450:         if (haveUninstalled) {
Chris@450:             m_beforeSearchLabel->setText(base);
Chris@450:         } else {
Chris@450:             m_beforeSearchLabel->setText
Chris@450:                 (base +
Chris@451:                  tr("<p>%1<br>Perhaps the network connection is down, services are responding too slowly, or a processing problem has occurred.<br>Only the descriptions of installed plugins will be searched.").arg(nopull));
Chris@450:         }
Chris@450:     } else {
Chris@450:         if (haveUninstalled) {
Chris@450:             m_beforeSearchLabel->setText
Chris@450:                 (base +
Chris@450:                  tr("<p>%1<br>Only the published descriptions of Vamp feature extraction plugins will be searched.").arg(noinst));
Chris@450:         } else {
Chris@450:             m_beforeSearchLabel->setText
Chris@450:                 (base +
Chris@450:                  tr("<p>%1<br>%2<br>Perhaps the network connection is down, or services are responding too slowly.<br>No search results will be available.").arg(noinst).arg(nopull));
Chris@450:         }
Chris@450:     }
Chris@450: 
Chris@450:     m_beforeSearchLabel->show();
Chris@450:     m_resultsFrame->resize(m_resultsFrame->sizeHint());
Chris@450: }
Chris@450: 
Chris@450: void
Chris@416: TransformFinder::searchTextChanged(const QString &text)
Chris@416: {
Chris@682: //    cerr << "text is " << text << endl;
Chris@424:     m_newSearchText = text;
Chris@424: }
Chris@416: 
Chris@424: void
Chris@424: TransformFinder::timeout()
Chris@424: {
Chris@448:     int maxResults = 60;
Chris@416:     
Chris@424:     if (m_newSearchText != "") {
Chris@416: 
Chris@424:         QString text = m_newSearchText;
Chris@424:         m_newSearchText = "";
Chris@424: 
Chris@424:         QStringList keywords = text.split(' ', QString::SkipEmptyParts);
Chris@424:         TransformFactory::SearchResults results =
Chris@424:             TransformFactory::getInstance()->search(keywords);
Chris@424:         
Chris@682: //        cerr << results.size() << " result(s)..." << endl;
Chris@424:         
Chris@431:         std::set<TextMatcher::Match> sorted;
Chris@424:         sorted.clear();
Chris@424:         for (TransformFactory::SearchResults::const_iterator j = results.begin();
Chris@424:              j != results.end(); ++j) {
Chris@424:             sorted.insert(j->second);
Chris@424:         }
Chris@448:         
Chris@424:         m_sortedResults.clear();
Chris@431:         for (std::set<TextMatcher::Match>::const_iterator j = sorted.end();
Chris@424:              j != sorted.begin(); ) {
Chris@424:             --j;
Chris@424:             m_sortedResults.push_back(*j);
Chris@807:             if ((int)m_sortedResults.size() == maxResults) break;
Chris@424:         }
Chris@424: 
Chris@424:         if (m_sortedResults.empty()) m_selectedTransform = "";
Chris@431:         else m_selectedTransform = m_sortedResults.begin()->key;
Chris@424: 
Chris@424:         m_upToDateCount = 0;
Chris@424: 
Chris@807:         for (int j = (int)m_labels.size(); j > (int)m_sortedResults.size(); ) {
Chris@424:             m_labels[--j]->hide();
Chris@424:         }
Chris@424: 
Chris@450:         m_beforeSearchLabel->hide();
Chris@450: 
Chris@448:         if (m_sortedResults.empty()) {
Chris@448:             m_noResultsLabel->show();
Chris@448:             m_resultsFrame->resize(m_resultsFrame->sizeHint());
Chris@448:         } else {
Chris@448:             m_noResultsLabel->hide();
Chris@448:         }
Chris@448: 
Chris@448:         if (m_sortedResults.size() < sorted.size()) {
Chris@448:             m_infoLabel->setText
Chris@448:                 (tr("Found %n description(s) containing <b>%1</b>, showing the first %2 only",
Chris@908:                     0, int(sorted.size())).arg(text).arg(m_sortedResults.size()));
Chris@448:         } else {
Chris@448:             m_infoLabel->setText
Chris@448:                 (tr("Found %n description(s) containing <b>%1</b>",
Chris@908:                     0, int(sorted.size())).arg(text));
Chris@448:         }
Chris@448: 
Chris@424:         return;
Chris@416:     }
Chris@416: 
Chris@807:     if (m_upToDateCount >= (int)m_sortedResults.size()) return;
Chris@425: 
Chris@807:     while (m_upToDateCount < (int)m_sortedResults.size()) {
Chris@417: 
Chris@424:         int i = m_upToDateCount;
Chris@416: 
Chris@682: //        cerr << "sorted size = " << m_sortedResults.size() << endl;
Chris@417: 
Chris@431:         TransformDescription desc;
Chris@431:         TransformId tid = m_sortedResults[i].key;
Chris@431:         TransformFactory *factory = TransformFactory::getInstance();
Chris@431:         TransformFactory::TransformInstallStatus status =
Chris@431:             factory->getTransformInstallStatus(tid);
Chris@431:         QString suffix;
Chris@431: 
Chris@431:         if (status == TransformFactory::TransformInstalled) {
Chris@431:             desc = factory->getTransformDescription(tid);
Chris@431:         } else {
Chris@431:             desc = factory->getUninstalledTransformDescription(tid);
Chris@431:             suffix = tr("<i> (not installed)</i>");
Chris@431:         }
Chris@419: 
Chris@419:         QString labelText;
Chris@431:         labelText += tr("%1%2<br><small>")
Chris@431:             .arg(XmlExportable::encodeEntities(desc.name))
Chris@431:             .arg(suffix);
Chris@424: 
Chris@416:         labelText += "...";
Chris@431:         for (TextMatcher::Match::FragmentMap::const_iterator k =
Chris@424:                  m_sortedResults[i].fragments.begin();
Chris@424:              k != m_sortedResults[i].fragments.end(); ++k) {
Chris@416:             labelText += k->second;
Chris@416:             labelText += "... ";
Chris@416:         }
Chris@416:         labelText += tr("</small>");
Chris@417: 
Chris@419:         QString selectedText;
Chris@431:         selectedText += tr("<b>%1</b>%2<br>")
Chris@431:             .arg(XmlExportable::encodeEntities
Chris@431:                  (desc.name == "" ? desc.identifier : desc.name))
Chris@431:             .arg(suffix);
Chris@431: 
Chris@431:         if (desc.longDescription != "") {
Chris@431:             selectedText += tr("<small>%1</small>")
Chris@431:                 .arg(XmlExportable::encodeEntities(desc.longDescription));
Chris@431:         } else if (desc.description != "") {
Chris@431:             selectedText += tr("<small>%1</small>")
Chris@431:                 .arg(XmlExportable::encodeEntities(desc.description));
Chris@431:         }
Chris@419: 
Chris@436:         selectedText += tr("<small>");
Chris@453:         if (desc.type != TransformDescription::UnknownType) {
Chris@436:             selectedText += tr("<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&mdash; Plugin type: %1")
Chris@453:                 .arg(XmlExportable::encodeEntities(factory->getTransformTypeName(desc.type)));
Chris@431:         }
Chris@431:         if (desc.category != "") {
Chris@436:             selectedText += tr("<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&mdash; Category: %1")
Chris@431:                 .arg(XmlExportable::encodeEntities(desc.category));
Chris@431:         }
Chris@436:         selectedText += tr("<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&mdash; System identifier: %1")
Chris@424:             .arg(XmlExportable::encodeEntities(desc.identifier));
Chris@436:         if (desc.infoUrl != "") {
Chris@436:             selectedText += tr("<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&mdash; More information: <a href=\"%1\">%1</a>")
Chris@436:                 .arg(desc.infoUrl);
Chris@436:         }
Chris@426:         selectedText += tr("</small>");
Chris@419: 
Chris@807:         if (i >= (int)m_labels.size()) {
Chris@419:             SelectableLabel *label = new SelectableLabel(m_resultsFrame);
Chris@421:             m_resultsLayout->addWidget(label);
Chris@420:             connect(label, SIGNAL(selectionChanged()), this,
Chris@420:                     SLOT(selectedLabelChanged()));
Chris@424:             connect(label, SIGNAL(doubleClicked()), this,
Chris@448:                     SLOT(labelDoubleClicked()));
Chris@421:             QPalette palette = label->palette();
Chris@421:             label->setPalette(palette);
Chris@417:             m_labels.push_back(label);
Chris@417:         }
Chris@421: 
Chris@420:         m_labels[i]->setObjectName(desc.identifier);
Chris@423:         m_labels[i]->setFixedWidth(this->width() - 40);
Chris@419:         m_labels[i]->setUnselectedText(labelText);
Chris@425: 
Chris@682: //        cerr << "selected text: " << selectedText << endl;
Chris@419:         m_labels[i]->setSelectedText(selectedText);
Chris@423: 
Chris@424:         m_labels[i]->setSelected(m_selectedTransform == desc.identifier);
Chris@425: 
Chris@425:         if (!m_labels[i]->isVisible()) m_labels[i]->show();
Chris@417: 
Chris@424:         ++m_upToDateCount;
Chris@424: 
Chris@425:         if (i == 0) break;
Chris@416:     }
Chris@425: 
Chris@425:     m_resultsFrame->resize(m_resultsFrame->sizeHint());
Chris@416: }
Chris@416: 
Chris@420: void
Chris@420: TransformFinder::selectedLabelChanged()
Chris@420: {
Chris@420:     QObject *s = sender();
Chris@420:     m_selectedTransform = "";
Chris@807:     for (int i = 0; i < (int)m_labels.size(); ++i) {
Chris@420:         if (!m_labels[i]->isVisible()) continue;
Chris@420:         if (m_labels[i] == s) {
Chris@420:             if (m_labels[i]->isSelected()) {
Chris@420:                 m_selectedTransform = m_labels[i]->objectName();
Chris@420:             }
Chris@420:         } else {
Chris@420:             if (m_labels[i]->isSelected()) {
Chris@420:                 m_labels[i]->setSelected(false);
Chris@420:             }
Chris@420:         }
Chris@420:     }
Chris@682:     cerr << "selectedLabelChanged: selected transform is now \""
Chris@682:               << m_selectedTransform << "\"" << endl;
Chris@420: }
Chris@420: 
Chris@448: void
Chris@448: TransformFinder::labelDoubleClicked()
Chris@448: {
Chris@448:     // The first click should have selected the label already
Chris@448:     if (TransformFactory::getInstance()->getTransformInstallStatus
Chris@448:         (m_selectedTransform) == 
Chris@448:         TransformFactory::TransformInstalled) {
Chris@448:         accept();
Chris@448:     }
Chris@448: }
Chris@448: 
Chris@416: TransformId
Chris@416: TransformFinder::getTransform() const
Chris@416: {
Chris@424:     return m_selectedTransform;
Chris@416: }
Chris@416: 
Chris@426: void
Chris@426: TransformFinder::up()
Chris@426: {
Chris@807:     for (int i = 0; i < (int)m_labels.size(); ++i) {
Chris@426:         if (!m_labels[i]->isVisible()) continue;
Chris@426:         if (m_labels[i]->objectName() == m_selectedTransform) {
Chris@426:             if (i > 0) {
Chris@426:                 m_labels[i]->setSelected(false);
Chris@426:                 m_labels[i-1]->setSelected(true);
Chris@426:                 m_selectedTransform = m_labels[i-1]->objectName();
Chris@426:             }
Chris@426:             return;
Chris@426:         }
Chris@426:     }
Chris@426: }
Chris@426: 
Chris@426: void
Chris@426: TransformFinder::down()
Chris@426: {
Chris@807:     for (int i = 0; i < (int)m_labels.size(); ++i) {
Chris@426:         if (!m_labels[i]->isVisible()) continue;
Chris@426:         if (m_labels[i]->objectName() == m_selectedTransform) {
Chris@807:             if (i+1 < (int)m_labels.size() &&
Chris@426:                 m_labels[i+1]->isVisible()) {
Chris@426:                 m_labels[i]->setSelected(false);
Chris@426:                 m_labels[i+1]->setSelected(true);
Chris@426:                 m_selectedTransform = m_labels[i+1]->objectName();
Chris@426:             }
Chris@426:             return;
Chris@426:         }
Chris@426:     }
Chris@426: }
Chris@426: