TransformFinder.cpp
Go to the documentation of this file.
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
2 
3 /*
4  Sonic Visualiser
5  An audio file viewer and annotation editor.
6  Centre for Digital Music, Queen Mary, University of London.
7  This file copyright 2008 QMUL.
8 
9  This program is free software; you can redistribute it and/or
10  modify it under the terms of the GNU General Public License as
11  published by the Free Software Foundation; either version 2 of the
12  License, or (at your option) any later version. See the file
13  COPYING included with this distribution for more information.
14 */
15 
16 #include "TransformFinder.h"
17 
18 #include "base/XmlExportable.h"
19 #include "transform/TransformFactory.h"
20 #include "SelectableLabel.h"
21 
22 #include <QVBoxLayout>
23 #include <QGridLayout>
24 #include <QLineEdit>
25 #include <QLabel>
26 #include <QDialogButtonBox>
27 #include <QScrollArea>
28 #include <QApplication>
29 #include <QScreen>
30 #include <QTimer>
31 #include <QAction>
32 
34  QDialog(parent),
35  m_resultsFrame(nullptr),
36  m_resultsLayout(nullptr)
37 {
38  setWindowTitle(tr("Find a Transform"));
39 
40  QGridLayout *mainGrid = new QGridLayout;
41  mainGrid->setVerticalSpacing(0);
42  setLayout(mainGrid);
43 
44  mainGrid->addWidget(new QLabel(tr("Find:")), 0, 0);
45 
46  QLineEdit *searchField = new QLineEdit;
47  mainGrid->addWidget(searchField, 0, 1);
48  connect(searchField, SIGNAL(textChanged(const QString &)),
49  this, SLOT(searchTextChanged(const QString &)));
50 
51 // m_infoLabel = new QLabel(tr("Type in this box to search descriptions of available and known transforms"));
52  m_infoLabel = new QLabel;
53  mainGrid->addWidget(m_infoLabel, 1, 1);
54 
55  m_resultsScroll = new QScrollArea;
56  mainGrid->addWidget(m_resultsScroll, 2, 0, 1, 2);
57  mainGrid->setRowStretch(2, 10);
58 
59  QDialogButtonBox *bb = new QDialogButtonBox(QDialogButtonBox::Ok |
60  QDialogButtonBox::Cancel);
61  mainGrid->addWidget(bb, 3, 0, 1, 2);
62  connect(bb, SIGNAL(accepted()), this, SLOT(accept()));
63  connect(bb, SIGNAL(rejected()), this, SLOT(reject()));
64 
65  m_resultsFrame = new QWidget;
66  QPalette palette = m_resultsFrame->palette();
67  palette.setColor(QPalette::Window, palette.color(QPalette::Base));
68  m_resultsFrame->setPalette(palette);
69  m_resultsScroll->setPalette(palette);
70  m_resultsLayout = new QVBoxLayout;
71  m_resultsLayout->setSpacing(0);
72  m_resultsLayout->setContentsMargins(0, 0, 0, 0);
73  m_resultsFrame->setLayout(m_resultsLayout);
74  m_resultsScroll->setWidget(m_resultsFrame);
75  m_resultsFrame->show();
76 
77  m_noResultsLabel = new QLabel(tr("<br>&nbsp;&nbsp;No results found"));
79  m_noResultsLabel->hide();
80 
81  m_beforeSearchLabel = new QLabel;
83  m_beforeSearchLabel->hide();
84 
85  QAction *up = new QAction(tr("Up"), this);
86  up->setShortcut(tr("Up"));
87  connect(up, SIGNAL(triggered()), this, SLOT(up()));
88  addAction(up);
89 
90  QAction *down = new QAction(tr("Down"), this);
91  down->setShortcut(tr("Down"));
92  connect(down, SIGNAL(triggered()), this, SLOT(down()));
93  addAction(down);
94 
95  QScreen *screen = QApplication::primaryScreen();
96  QRect available = screen->availableGeometry();
97 
98  int width = available.width() / 2;
99  int height = available.height() / 2;
100  if (height < 450) {
101  if (available.height() > 500) height = 450;
102  }
103  if (width < 600) {
104  if (available.width() > 650) width = 600;
105  }
106 
107  resize(width, height);
108  raise();
109 
111 
112  m_upToDateCount = 0;
113  m_timer = new QTimer(this);
114  connect(m_timer, SIGNAL(timeout()), this, SLOT(timeout()));
115  m_timer->start(30);
116 }
117 
119 {
120 }
121 
122 void
124 {
125  bool haveInstalled =
126  TransformFactory::getInstance()->haveInstalledTransforms();
127  bool haveUninstalled =
128  TransformFactory::getInstance()->haveUninstalledTransforms();
129 
130  m_beforeSearchLabel->setWordWrap(true);
131  m_beforeSearchLabel->setOpenExternalLinks(true);
132  m_beforeSearchLabel->setTextInteractionFlags(Qt::LinksAccessibleByMouse);
133  m_beforeSearchLabel->setMargin(12);
134  m_beforeSearchLabel->setFixedWidth(this->width() - 40);
135 
136  QString base =
137  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>");
138 
139  QString nopull =
140  tr("<b>Unable to retrieve published descriptions from network!</b>");
141 
142  QString noinst =
143  tr("<b>No plugins are currently installed!</b>");
144 
145  if (haveInstalled) {
146  if (haveUninstalled) {
147  m_beforeSearchLabel->setText(base);
148  } else {
149  m_beforeSearchLabel->setText
150  (base +
151  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));
152  }
153  } else {
154  if (haveUninstalled) {
155  m_beforeSearchLabel->setText
156  (base +
157  tr("<p>%1<br>Only the published descriptions of Vamp feature extraction plugins will be searched.").arg(noinst));
158  } else {
159  m_beforeSearchLabel->setText
160  (base +
161  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));
162  }
163  }
164 
165  m_beforeSearchLabel->show();
166  m_resultsFrame->resize(m_resultsFrame->sizeHint());
167 }
168 
169 void
171 {
172 // cerr << "text is " << text << endl;
173  m_newSearchText = text;
174 }
175 
176 void
178 {
179  int maxResults = 60;
180 
181  if (m_newSearchText != "") {
182 
183  QString text = m_newSearchText;
184  m_newSearchText = "";
185 
186  QStringList keywords = text.split(' ', QString::SkipEmptyParts);
187  TransformFactory::SearchResults results =
188  TransformFactory::getInstance()->search(keywords);
189 
190 // cerr << results.size() << " result(s)..." << endl;
191 
192  std::set<TextMatcher::Match> sorted;
193  sorted.clear();
194  for (TransformFactory::SearchResults::const_iterator j = results.begin();
195  j != results.end(); ++j) {
196  sorted.insert(j->second);
197  }
198 
199  m_sortedResults.clear();
200  for (std::set<TextMatcher::Match>::const_iterator j = sorted.end();
201  j != sorted.begin(); ) {
202  --j;
203  m_sortedResults.push_back(*j);
204  if ((int)m_sortedResults.size() == maxResults) break;
205  }
206 
207  if (m_sortedResults.empty()) m_selectedTransform = "";
208  else m_selectedTransform = m_sortedResults.begin()->key;
209 
210  m_upToDateCount = 0;
211 
212  for (int j = (int)m_labels.size(); j > (int)m_sortedResults.size(); ) {
213  m_labels[--j]->hide();
214  }
215 
216  m_beforeSearchLabel->hide();
217 
218  if (m_sortedResults.empty()) {
219  m_noResultsLabel->show();
220  m_resultsFrame->resize(m_resultsFrame->sizeHint());
221  } else {
222  m_noResultsLabel->hide();
223  }
224 
225  if (m_sortedResults.size() < sorted.size()) {
226  m_infoLabel->setText
227  (tr("Found %n description(s) containing <b>%1</b>, showing the first %2 only",
228  nullptr, int(sorted.size())).arg(text).arg(m_sortedResults.size()));
229  } else {
230  m_infoLabel->setText
231  (tr("Found %n description(s) containing <b>%1</b>",
232  nullptr, int(sorted.size())).arg(text));
233  }
234 
235  return;
236  }
237 
238  if (m_upToDateCount >= (int)m_sortedResults.size()) return;
239 
240  while (m_upToDateCount < (int)m_sortedResults.size()) {
241 
242  int i = m_upToDateCount;
243 
244 // cerr << "sorted size = " << m_sortedResults.size() << endl;
245 
246  TransformDescription desc;
247  TransformId tid = m_sortedResults[i].key;
248  TransformFactory *factory = TransformFactory::getInstance();
249  TransformFactory::TransformInstallStatus status =
250  factory->getTransformInstallStatus(tid);
251  QString suffix;
252 
253  if (status == TransformFactory::TransformInstalled) {
254  desc = factory->getTransformDescription(tid);
255  } else {
256  desc = factory->getUninstalledTransformDescription(tid);
257  suffix = tr("<i> (not installed)</i>");
258  }
259 
260  QString labelText;
261  labelText += tr("%1%2<br><small>")
262  .arg(XmlExportable::encodeEntities(desc.name))
263  .arg(suffix);
264 
265  labelText += "...";
266  for (TextMatcher::Match::FragmentMap::const_iterator k =
267  m_sortedResults[i].fragments.begin();
268  k != m_sortedResults[i].fragments.end(); ++k) {
269  labelText += k->second;
270  labelText += "... ";
271  }
272  labelText += tr("</small>");
273 
274  QString selectedText;
275  selectedText += tr("<b>%1</b>%2<br>")
276  .arg(XmlExportable::encodeEntities
277  (desc.name == "" ? desc.identifier : desc.name))
278  .arg(suffix);
279 
280  if (desc.longDescription != "") {
281  selectedText += tr("<small>%1</small>")
282  .arg(XmlExportable::encodeEntities(desc.longDescription));
283  } else if (desc.description != "") {
284  selectedText += tr("<small>%1</small>")
285  .arg(XmlExportable::encodeEntities(desc.description));
286  }
287 
288  QString indentation = tr("&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&mdash;");
289 
290  selectedText += tr("<small>");
291  if (desc.type != TransformDescription::UnknownType) {
292  selectedText += tr("<br>%1 Plugin type: %2")
293  .arg(indentation)
294  .arg(XmlExportable::encodeEntities
295  (factory->getTransformTypeName(desc.type)));
296  }
297  if (desc.category != "") {
298  selectedText += tr("<br>%1 Category: %2")
299  .arg(indentation)
300  .arg(XmlExportable::encodeEntities(desc.category));
301  }
302  selectedText += tr("<br>%1 System identifier: %2")
303  .arg(indentation)
304  .arg(XmlExportable::encodeEntities(desc.identifier));
305  if (desc.provider.infoUrl != "") {
306  selectedText += tr("<br>%1 More information: <a href=\"%2\">%2</a>")
307  .arg(indentation)
308  .arg(desc.provider.infoUrl);
309  }
310  if (status != TransformFactory::TransformInstalled) {
311  bool haveSuitableDownloadLink =
312  (desc.provider.downloadUrl != "" &&
313  desc.provider.hasDownloadForThisPlatform());
314  if (haveSuitableDownloadLink) {
315  selectedText += tr("<br>%1 Download for %2: <a href=\"%3\">%3</a>")
316  .arg(indentation)
317  .arg(desc.provider.thisPlatformName())
318  .arg(desc.provider.downloadUrl);
319  }
320  if (!desc.provider.foundInPacks.empty()) {
321  QStringList packsLinks;
322  for (auto p: desc.provider.foundInPacks) {
323  packsLinks <<
324  tr("<a href=\"%1\">%2</a>").arg(p.second).arg(p.first);
325  }
326  selectedText +=
327  (haveSuitableDownloadLink ?
328  tr("<br>%1 Also available in: %2") :
329  tr("<br>%1 Available in: %2"))
330  .arg(indentation)
331  .arg(packsLinks.join(tr(", ")));
332  }
333  }
334 
335  selectedText += tr("</small>");
336 
337  if (i >= (int)m_labels.size()) {
339  m_resultsLayout->addWidget(label);
340  connect(label, SIGNAL(selectionChanged()), this,
341  SLOT(selectedLabelChanged()));
342  connect(label, SIGNAL(doubleClicked()), this,
343  SLOT(labelDoubleClicked()));
344  QPalette palette = label->palette();
345  label->setPalette(palette);
346  m_labels.push_back(label);
347  }
348 
349  m_labels[i]->setObjectName(desc.identifier);
350  m_labels[i]->setFixedWidth(this->width() - 40);
351  m_labels[i]->setUnselectedText(labelText);
352 
353 // cerr << "selected text: " << selectedText << endl;
354  m_labels[i]->setSelectedText(selectedText);
355 
356  m_labels[i]->setSelected(m_selectedTransform == desc.identifier);
357 
358  if (!m_labels[i]->isVisible()) m_labels[i]->show();
359 
360  ++m_upToDateCount;
361 
362  if (i == 0) break;
363  }
364 
365  m_resultsFrame->resize(m_resultsFrame->sizeHint());
366 }
367 
368 void
370 {
371  QObject *s = sender();
372  m_selectedTransform = "";
373  for (int i = 0; i < (int)m_labels.size(); ++i) {
374  if (!m_labels[i]->isVisible()) continue;
375  if (m_labels[i] == s) {
376  if (m_labels[i]->isSelected()) {
377  m_selectedTransform = m_labels[i]->objectName();
378  }
379  } else {
380  if (m_labels[i]->isSelected()) {
381  m_labels[i]->setSelected(false);
382  }
383  }
384  }
385  cerr << "selectedLabelChanged: selected transform is now \""
386  << m_selectedTransform << "\"" << endl;
387 }
388 
389 void
391 {
392  // The first click should have selected the label already
393  if (TransformFactory::getInstance()->getTransformInstallStatus
394  (m_selectedTransform) ==
395  TransformFactory::TransformInstalled) {
396  accept();
397  }
398 }
399 
400 TransformId
402 {
403  return m_selectedTransform;
404 }
405 
406 void
408 {
409  for (int i = 0; i < (int)m_labels.size(); ++i) {
410  if (!m_labels[i]->isVisible()) continue;
411  if (m_labels[i]->objectName() == m_selectedTransform) {
412  if (i > 0) {
413  m_labels[i]->setSelected(false);
414  m_labels[i-1]->setSelected(true);
415  m_selectedTransform = m_labels[i-1]->objectName();
416  }
417  return;
418  }
419  }
420 }
421 
422 void
424 {
425  for (int i = 0; i < (int)m_labels.size(); ++i) {
426  if (!m_labels[i]->isVisible()) continue;
427  if (m_labels[i]->objectName() == m_selectedTransform) {
428  if (i+1 < (int)m_labels.size() &&
429  m_labels[i+1]->isVisible()) {
430  m_labels[i]->setSelected(false);
431  m_labels[i+1]->setSelected(true);
432  m_selectedTransform = m_labels[i+1]->objectName();
433  }
434  return;
435  }
436  }
437 }
438 
QWidget * m_resultsFrame
TransformId getTransform() const
QLabel * m_infoLabel
void searchTextChanged(const QString &)
QLabel * m_noResultsLabel
QVBoxLayout * m_resultsLayout
TransformId m_selectedTransform
QLabel * m_beforeSearchLabel
TransformFinder(QWidget *parent=0)
QScrollArea * m_resultsScroll
QString m_newSearchText
std::vector< SelectableLabel * > m_labels
SortedResults m_sortedResults