annotate transform/TransformFactory.cpp @ 33:544ab25d2372

* Add support for plugin classification using category files. Add separate menus listing plugins by category, maker, and plugin name.
author Chris Cannam
date Thu, 21 Sep 2006 16:43:50 +0000
parents b5f55ea61bb8
children 8ad306d8a568
rev   line source
Chris@0 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@0 2
Chris@0 3 /*
Chris@0 4 Sonic Visualiser
Chris@0 5 An audio file viewer and annotation editor.
Chris@0 6 Centre for Digital Music, Queen Mary, University of London.
Chris@0 7 This file copyright 2006 Chris Cannam.
Chris@0 8
Chris@0 9 This program is free software; you can redistribute it and/or
Chris@0 10 modify it under the terms of the GNU General Public License as
Chris@0 11 published by the Free Software Foundation; either version 2 of the
Chris@0 12 License, or (at your option) any later version. See the file
Chris@0 13 COPYING included with this distribution for more information.
Chris@0 14 */
Chris@0 15
Chris@0 16 #include "TransformFactory.h"
Chris@0 17
Chris@0 18 #include "FeatureExtractionPluginTransform.h"
Chris@0 19 #include "RealTimePluginTransform.h"
Chris@0 20
Chris@0 21 #include "plugin/FeatureExtractionPluginFactory.h"
Chris@0 22 #include "plugin/RealTimePluginFactory.h"
Chris@0 23 #include "plugin/PluginXml.h"
Chris@0 24
Chris@0 25 #include "widgets/PluginParameterDialog.h"
Chris@0 26
Chris@1 27 #include "data/model/DenseTimeValueModel.h"
Chris@0 28
Chris@28 29 #include "vamp-sdk/PluginHostAdapter.h"
Chris@28 30
Chris@0 31 #include <iostream>
Chris@0 32 #include <set>
Chris@0 33
Chris@0 34 #include <QRegExp>
Chris@0 35
Chris@0 36 TransformFactory *
Chris@0 37 TransformFactory::m_instance = new TransformFactory;
Chris@0 38
Chris@0 39 TransformFactory *
Chris@0 40 TransformFactory::getInstance()
Chris@0 41 {
Chris@0 42 return m_instance;
Chris@0 43 }
Chris@0 44
Chris@0 45 TransformFactory::~TransformFactory()
Chris@0 46 {
Chris@0 47 }
Chris@0 48
Chris@0 49 TransformFactory::TransformList
Chris@0 50 TransformFactory::getAllTransforms()
Chris@0 51 {
Chris@0 52 if (m_transforms.empty()) populateTransforms();
Chris@0 53
Chris@0 54 TransformList list;
Chris@0 55 for (TransformDescriptionMap::const_iterator i = m_transforms.begin();
Chris@0 56 i != m_transforms.end(); ++i) {
Chris@0 57 list.push_back(i->second);
Chris@0 58 }
Chris@0 59
Chris@0 60 return list;
Chris@0 61 }
Chris@0 62
Chris@0 63 std::vector<QString>
Chris@0 64 TransformFactory::getAllTransformTypes()
Chris@0 65 {
Chris@0 66 if (m_transforms.empty()) populateTransforms();
Chris@0 67
Chris@0 68 std::set<QString> types;
Chris@0 69 for (TransformDescriptionMap::const_iterator i = m_transforms.begin();
Chris@0 70 i != m_transforms.end(); ++i) {
Chris@0 71 types.insert(i->second.type);
Chris@0 72 }
Chris@0 73
Chris@0 74 std::vector<QString> rv;
Chris@0 75 for (std::set<QString>::iterator i = types.begin(); i != types.end(); ++i) {
Chris@0 76 rv.push_back(*i);
Chris@0 77 }
Chris@0 78
Chris@0 79 return rv;
Chris@0 80 }
Chris@0 81
Chris@33 82 std::vector<QString>
Chris@33 83 TransformFactory::getTransformCategories(QString transformType)
Chris@33 84 {
Chris@33 85 if (m_transforms.empty()) populateTransforms();
Chris@33 86
Chris@33 87 std::set<QString> categories;
Chris@33 88 for (TransformDescriptionMap::const_iterator i = m_transforms.begin();
Chris@33 89 i != m_transforms.end(); ++i) {
Chris@33 90 if (i->second.type == transformType) {
Chris@33 91 categories.insert(i->second.category);
Chris@33 92 }
Chris@33 93 }
Chris@33 94
Chris@33 95 bool haveEmpty = false;
Chris@33 96
Chris@33 97 std::vector<QString> rv;
Chris@33 98 for (std::set<QString>::iterator i = categories.begin();
Chris@33 99 i != categories.end(); ++i) {
Chris@33 100 if (*i != "") rv.push_back(*i);
Chris@33 101 else haveEmpty = true;
Chris@33 102 }
Chris@33 103
Chris@33 104 if (haveEmpty) rv.push_back(""); // make sure empty category sorts last
Chris@33 105
Chris@33 106 return rv;
Chris@33 107 }
Chris@33 108
Chris@33 109 std::vector<QString>
Chris@33 110 TransformFactory::getTransformMakers(QString transformType)
Chris@33 111 {
Chris@33 112 if (m_transforms.empty()) populateTransforms();
Chris@33 113
Chris@33 114 std::set<QString> makers;
Chris@33 115 for (TransformDescriptionMap::const_iterator i = m_transforms.begin();
Chris@33 116 i != m_transforms.end(); ++i) {
Chris@33 117 if (i->second.type == transformType) {
Chris@33 118 makers.insert(i->second.maker);
Chris@33 119 }
Chris@33 120 }
Chris@33 121
Chris@33 122 bool haveEmpty = false;
Chris@33 123
Chris@33 124 std::vector<QString> rv;
Chris@33 125 for (std::set<QString>::iterator i = makers.begin();
Chris@33 126 i != makers.end(); ++i) {
Chris@33 127 if (*i != "") rv.push_back(*i);
Chris@33 128 else haveEmpty = true;
Chris@33 129 }
Chris@33 130
Chris@33 131 if (haveEmpty) rv.push_back(""); // make sure empty category sorts last
Chris@33 132
Chris@33 133 return rv;
Chris@33 134 }
Chris@33 135
Chris@0 136 void
Chris@0 137 TransformFactory::populateTransforms()
Chris@0 138 {
Chris@0 139 TransformDescriptionMap transforms;
Chris@0 140
Chris@0 141 populateFeatureExtractionPlugins(transforms);
Chris@0 142 populateRealTimePlugins(transforms);
Chris@0 143
Chris@0 144 // disambiguate plugins with similar descriptions
Chris@0 145
Chris@0 146 std::map<QString, int> descriptions;
Chris@0 147
Chris@0 148 for (TransformDescriptionMap::iterator i = transforms.begin();
Chris@0 149 i != transforms.end(); ++i) {
Chris@0 150
Chris@0 151 TransformDesc desc = i->second;
Chris@0 152
Chris@0 153 ++descriptions[desc.description];
Chris@0 154 ++descriptions[QString("%1 [%2]").arg(desc.description).arg(desc.maker)];
Chris@0 155 }
Chris@0 156
Chris@0 157 std::map<QString, int> counts;
Chris@0 158 m_transforms.clear();
Chris@0 159
Chris@0 160 for (TransformDescriptionMap::iterator i = transforms.begin();
Chris@0 161 i != transforms.end(); ++i) {
Chris@0 162
Chris@0 163 TransformDesc desc = i->second;
Chris@0 164 QString name = desc.name;
Chris@0 165 QString description = desc.description;
Chris@0 166 QString maker = desc.maker;
Chris@0 167
Chris@0 168 if (descriptions[description] > 1) {
Chris@0 169 description = QString("%1 [%2]").arg(description).arg(maker);
Chris@0 170 if (descriptions[description] > 1) {
Chris@0 171 description = QString("%1 <%2>")
Chris@0 172 .arg(description).arg(++counts[description]);
Chris@0 173 }
Chris@0 174 }
Chris@0 175
Chris@0 176 desc.description = description;
Chris@0 177 m_transforms[name] = desc;
Chris@0 178 }
Chris@0 179 }
Chris@0 180
Chris@0 181 void
Chris@0 182 TransformFactory::populateFeatureExtractionPlugins(TransformDescriptionMap &transforms)
Chris@0 183 {
Chris@0 184 std::vector<QString> plugs =
Chris@0 185 FeatureExtractionPluginFactory::getAllPluginIdentifiers();
Chris@0 186
Chris@0 187 for (size_t i = 0; i < plugs.size(); ++i) {
Chris@0 188
Chris@0 189 QString pluginId = plugs[i];
Chris@0 190
Chris@0 191 FeatureExtractionPluginFactory *factory =
Chris@0 192 FeatureExtractionPluginFactory::instanceFor(pluginId);
Chris@0 193
Chris@0 194 if (!factory) {
Chris@0 195 std::cerr << "WARNING: TransformFactory::populateTransforms: No feature extraction plugin factory for instance " << pluginId.toLocal8Bit().data() << std::endl;
Chris@0 196 continue;
Chris@0 197 }
Chris@0 198
Chris@0 199 Vamp::Plugin *plugin =
Chris@0 200 factory->instantiatePlugin(pluginId, 48000);
Chris@0 201
Chris@0 202 if (!plugin) {
Chris@0 203 std::cerr << "WARNING: TransformFactory::populateTransforms: Failed to instantiate plugin " << pluginId.toLocal8Bit().data() << std::endl;
Chris@0 204 continue;
Chris@0 205 }
Chris@0 206
Chris@0 207 QString pluginDescription = plugin->getDescription().c_str();
Chris@33 208 QString category = factory->getPluginCategory(pluginId);
Chris@33 209
Chris@0 210 Vamp::Plugin::OutputList outputs =
Chris@0 211 plugin->getOutputDescriptors();
Chris@0 212
Chris@0 213 for (size_t j = 0; j < outputs.size(); ++j) {
Chris@0 214
Chris@0 215 QString transformName = QString("%1:%2")
Chris@0 216 .arg(pluginId).arg(outputs[j].name.c_str());
Chris@0 217
Chris@0 218 QString userDescription;
Chris@0 219 QString friendlyName;
Chris@0 220 QString units = outputs[j].unit.c_str();
Chris@0 221
Chris@0 222 if (outputs.size() == 1) {
Chris@0 223 userDescription = pluginDescription;
Chris@0 224 friendlyName = pluginDescription;
Chris@0 225 } else {
Chris@0 226 userDescription = QString("%1: %2")
Chris@0 227 .arg(pluginDescription)
Chris@0 228 .arg(outputs[j].description.c_str());
Chris@0 229 friendlyName = outputs[j].description.c_str();
Chris@0 230 }
Chris@0 231
Chris@0 232 bool configurable = (!plugin->getPrograms().empty() ||
Chris@0 233 !plugin->getParameterDescriptors().empty());
Chris@0 234
Chris@0 235 transforms[transformName] =
Chris@0 236 TransformDesc(tr("Analysis Plugins"),
Chris@33 237 category,
Chris@0 238 transformName,
Chris@0 239 userDescription,
Chris@0 240 friendlyName,
Chris@0 241 plugin->getMaker().c_str(),
Chris@0 242 units,
Chris@0 243 configurable);
Chris@0 244 }
Chris@0 245 }
Chris@0 246 }
Chris@0 247
Chris@0 248 void
Chris@0 249 TransformFactory::populateRealTimePlugins(TransformDescriptionMap &transforms)
Chris@0 250 {
Chris@0 251 std::vector<QString> plugs =
Chris@0 252 RealTimePluginFactory::getAllPluginIdentifiers();
Chris@0 253
Chris@0 254 QRegExp unitRE("[\\[\\(]([A-Za-z0-9/]+)[\\)\\]]$");
Chris@0 255
Chris@0 256 for (size_t i = 0; i < plugs.size(); ++i) {
Chris@0 257
Chris@0 258 QString pluginId = plugs[i];
Chris@0 259
Chris@0 260 RealTimePluginFactory *factory =
Chris@0 261 RealTimePluginFactory::instanceFor(pluginId);
Chris@0 262
Chris@0 263 if (!factory) {
Chris@0 264 std::cerr << "WARNING: TransformFactory::populateTransforms: No real time plugin factory for instance " << pluginId.toLocal8Bit().data() << std::endl;
Chris@0 265 continue;
Chris@0 266 }
Chris@0 267
Chris@0 268 const RealTimePluginDescriptor *descriptor =
Chris@0 269 factory->getPluginDescriptor(pluginId);
Chris@0 270
Chris@0 271 if (!descriptor) {
Chris@0 272 std::cerr << "WARNING: TransformFactory::populateTransforms: Failed to query plugin " << pluginId.toLocal8Bit().data() << std::endl;
Chris@0 273 continue;
Chris@0 274 }
Chris@0 275
Chris@0 276 if (descriptor->controlOutputPortCount == 0 ||
Chris@0 277 descriptor->audioInputPortCount == 0) continue;
Chris@0 278
Chris@0 279 // std::cout << "TransformFactory::populateRealTimePlugins: plugin " << pluginId.toStdString() << " has " << descriptor->controlOutputPortCount << " output ports" << std::endl;
Chris@0 280
Chris@0 281 QString pluginDescription = descriptor->name.c_str();
Chris@33 282 QString category = factory->getPluginCategory(pluginId);
Chris@0 283
Chris@0 284 for (size_t j = 0; j < descriptor->controlOutputPortCount; ++j) {
Chris@0 285
Chris@0 286 QString transformName = QString("%1:%2").arg(pluginId).arg(j);
Chris@0 287 QString userDescription;
Chris@0 288 QString units;
Chris@0 289
Chris@0 290 if (j < descriptor->controlOutputPortNames.size() &&
Chris@0 291 descriptor->controlOutputPortNames[j] != "") {
Chris@0 292
Chris@0 293 QString portName = descriptor->controlOutputPortNames[j].c_str();
Chris@0 294
Chris@0 295 userDescription = tr("%1: %2")
Chris@0 296 .arg(pluginDescription)
Chris@0 297 .arg(portName);
Chris@0 298
Chris@0 299 if (unitRE.indexIn(portName) >= 0) {
Chris@0 300 units = unitRE.cap(1);
Chris@0 301 }
Chris@0 302
Chris@0 303 } else if (descriptor->controlOutputPortCount > 1) {
Chris@0 304
Chris@0 305 userDescription = tr("%1: Output %2")
Chris@0 306 .arg(pluginDescription)
Chris@0 307 .arg(j + 1);
Chris@0 308
Chris@0 309 } else {
Chris@0 310
Chris@0 311 userDescription = pluginDescription;
Chris@0 312 }
Chris@0 313
Chris@0 314
Chris@0 315 bool configurable = (descriptor->parameterCount > 0);
Chris@0 316
Chris@0 317 transforms[transformName] =
Chris@0 318 TransformDesc(tr("Other Plugins"),
Chris@33 319 category,
Chris@0 320 transformName,
Chris@0 321 userDescription,
Chris@0 322 userDescription,
Chris@0 323 descriptor->maker.c_str(),
Chris@0 324 units,
Chris@0 325 configurable);
Chris@0 326 }
Chris@0 327 }
Chris@0 328 }
Chris@0 329
Chris@0 330 QString
Chris@0 331 TransformFactory::getTransformDescription(TransformName name)
Chris@0 332 {
Chris@0 333 if (m_transforms.find(name) != m_transforms.end()) {
Chris@0 334 return m_transforms[name].description;
Chris@0 335 } else return "";
Chris@0 336 }
Chris@0 337
Chris@0 338 QString
Chris@0 339 TransformFactory::getTransformFriendlyName(TransformName name)
Chris@0 340 {
Chris@0 341 if (m_transforms.find(name) != m_transforms.end()) {
Chris@0 342 return m_transforms[name].friendlyName;
Chris@0 343 } else return "";
Chris@0 344 }
Chris@0 345
Chris@0 346 QString
Chris@0 347 TransformFactory::getTransformUnits(TransformName name)
Chris@0 348 {
Chris@0 349 if (m_transforms.find(name) != m_transforms.end()) {
Chris@0 350 return m_transforms[name].units;
Chris@0 351 } else return "";
Chris@0 352 }
Chris@0 353
Chris@0 354 bool
Chris@0 355 TransformFactory::isTransformConfigurable(TransformName name)
Chris@0 356 {
Chris@0 357 if (m_transforms.find(name) != m_transforms.end()) {
Chris@0 358 return m_transforms[name].configurable;
Chris@0 359 } else return false;
Chris@0 360 }
Chris@0 361
Chris@0 362 bool
Chris@0 363 TransformFactory::getTransformChannelRange(TransformName name,
Chris@0 364 int &min, int &max)
Chris@0 365 {
Chris@0 366 QString id = name.section(':', 0, 2);
Chris@0 367
Chris@0 368 if (FeatureExtractionPluginFactory::instanceFor(id)) {
Chris@0 369
Chris@0 370 Vamp::Plugin *plugin =
Chris@0 371 FeatureExtractionPluginFactory::instanceFor(id)->
Chris@0 372 instantiatePlugin(id, 48000);
Chris@0 373 if (!plugin) return false;
Chris@0 374
Chris@0 375 min = plugin->getMinChannelCount();
Chris@0 376 max = plugin->getMaxChannelCount();
Chris@0 377 delete plugin;
Chris@0 378
Chris@0 379 return true;
Chris@0 380
Chris@0 381 } else if (RealTimePluginFactory::instanceFor(id)) {
Chris@0 382
Chris@0 383 const RealTimePluginDescriptor *descriptor =
Chris@0 384 RealTimePluginFactory::instanceFor(id)->
Chris@0 385 getPluginDescriptor(id);
Chris@0 386 if (!descriptor) return false;
Chris@0 387
Chris@0 388 min = descriptor->audioInputPortCount;
Chris@0 389 max = descriptor->audioInputPortCount;
Chris@0 390
Chris@0 391 return true;
Chris@0 392 }
Chris@0 393
Chris@0 394 return false;
Chris@0 395 }
Chris@0 396
Chris@0 397 bool
Chris@0 398 TransformFactory::getChannelRange(TransformName name, Vamp::PluginBase *plugin,
Chris@0 399 int &minChannels, int &maxChannels)
Chris@0 400 {
Chris@0 401 Vamp::Plugin *vp = 0;
Chris@28 402 if ((vp = dynamic_cast<Vamp::Plugin *>(plugin)) ||
Chris@28 403 (vp = dynamic_cast<Vamp::PluginHostAdapter *>(plugin))) {
Chris@0 404 minChannels = vp->getMinChannelCount();
Chris@0 405 maxChannels = vp->getMaxChannelCount();
Chris@0 406 return true;
Chris@0 407 } else {
Chris@0 408 return getTransformChannelRange(name, minChannels, maxChannels);
Chris@0 409 }
Chris@0 410 }
Chris@0 411
Chris@0 412 bool
Chris@0 413 TransformFactory::getConfigurationForTransform(TransformName name,
Chris@0 414 Model *inputModel,
Chris@27 415 PluginTransform::ExecutionContext &context,
Chris@0 416 QString &configurationXml)
Chris@0 417 {
Chris@0 418 QString id = name.section(':', 0, 2);
Chris@0 419 QString output = name.section(':', 3);
Chris@0 420
Chris@0 421 bool ok = false;
Chris@0 422 configurationXml = m_lastConfigurations[name];
Chris@0 423
Chris@0 424 // std::cerr << "last configuration: " << configurationXml.toStdString() << std::endl;
Chris@0 425
Chris@0 426 Vamp::PluginBase *plugin = 0;
Chris@0 427
Chris@10 428 bool frequency = false;
Chris@10 429
Chris@0 430 if (FeatureExtractionPluginFactory::instanceFor(id)) {
Chris@0 431
Chris@10 432 Vamp::Plugin *vp =
Chris@10 433 FeatureExtractionPluginFactory::instanceFor(id)->instantiatePlugin
Chris@0 434 (id, inputModel->getSampleRate());
Chris@10 435 if (vp) {
Chris@10 436 plugin = vp;
Chris@10 437 frequency = (vp->getInputDomain() == Vamp::Plugin::FrequencyDomain);
Chris@10 438 }
Chris@0 439
Chris@0 440 } else if (RealTimePluginFactory::instanceFor(id)) {
Chris@0 441
Chris@0 442 plugin = RealTimePluginFactory::instanceFor(id)->instantiatePlugin
Chris@0 443 (id, 0, 0, inputModel->getSampleRate(), 1024, 1);
Chris@0 444 }
Chris@0 445
Chris@0 446 if (plugin) {
Chris@27 447
Chris@27 448 context = PluginTransform::ExecutionContext(context.channel, plugin);
Chris@27 449
Chris@0 450 if (configurationXml != "") {
Chris@0 451 PluginXml(plugin).setParametersFromXml(configurationXml);
Chris@0 452 }
Chris@0 453
Chris@0 454 int sourceChannels = 1;
Chris@0 455 if (dynamic_cast<DenseTimeValueModel *>(inputModel)) {
Chris@0 456 sourceChannels = dynamic_cast<DenseTimeValueModel *>(inputModel)
Chris@0 457 ->getChannelCount();
Chris@0 458 }
Chris@0 459
Chris@0 460 int minChannels = 1, maxChannels = sourceChannels;
Chris@0 461 getChannelRange(name, plugin, minChannels, maxChannels);
Chris@0 462
Chris@0 463 int targetChannels = sourceChannels;
Chris@0 464 if (sourceChannels < minChannels) targetChannels = minChannels;
Chris@0 465 if (sourceChannels > maxChannels) targetChannels = maxChannels;
Chris@0 466
Chris@27 467 int defaultChannel = context.channel;
Chris@0 468
Chris@0 469 PluginParameterDialog *dialog = new PluginParameterDialog(plugin,
Chris@0 470 sourceChannels,
Chris@0 471 targetChannels,
Chris@0 472 defaultChannel,
Chris@10 473 output,
Chris@10 474 true,
Chris@10 475 frequency);
Chris@0 476 if (dialog->exec() == QDialog::Accepted) {
Chris@0 477 ok = true;
Chris@0 478 }
Chris@0 479 configurationXml = PluginXml(plugin).toXmlString();
Chris@27 480 context.channel = dialog->getChannel();
Chris@26 481
Chris@27 482 dialog->getProcessingParameters(context.stepSize,
Chris@27 483 context.blockSize,
Chris@27 484 context.windowType);
Chris@27 485
Chris@27 486 context.makeConsistentWithPlugin(plugin);
Chris@26 487
Chris@0 488 delete dialog;
Chris@0 489 delete plugin;
Chris@0 490 }
Chris@0 491
Chris@0 492 if (ok) m_lastConfigurations[name] = configurationXml;
Chris@0 493
Chris@0 494 return ok;
Chris@0 495 }
Chris@0 496
Chris@0 497 Transform *
Chris@0 498 TransformFactory::createTransform(TransformName name, Model *inputModel,
Chris@27 499 const PluginTransform::ExecutionContext &context,
Chris@27 500 QString configurationXml, bool start)
Chris@0 501 {
Chris@0 502 Transform *transform = 0;
Chris@0 503
Chris@0 504 QString id = name.section(':', 0, 2);
Chris@0 505 QString output = name.section(':', 3);
Chris@0 506
Chris@0 507 if (FeatureExtractionPluginFactory::instanceFor(id)) {
Chris@0 508 transform = new FeatureExtractionPluginTransform(inputModel,
Chris@0 509 id,
Chris@27 510 context,
Chris@0 511 configurationXml,
Chris@0 512 output);
Chris@0 513 } else if (RealTimePluginFactory::instanceFor(id)) {
Chris@0 514 transform = new RealTimePluginTransform(inputModel,
Chris@0 515 id,
Chris@27 516 context,
Chris@0 517 configurationXml,
Chris@0 518 getTransformUnits(name),
Chris@0 519 output.toInt());
Chris@0 520 } else {
Chris@0 521 std::cerr << "TransformFactory::createTransform: Unknown transform \""
Chris@0 522 << name.toStdString() << "\"" << std::endl;
Chris@0 523 return transform;
Chris@0 524 }
Chris@0 525
Chris@0 526 if (start && transform) transform->start();
Chris@0 527 transform->setObjectName(name);
Chris@0 528 return transform;
Chris@0 529 }
Chris@0 530
Chris@0 531 Model *
Chris@0 532 TransformFactory::transform(TransformName name, Model *inputModel,
Chris@27 533 const PluginTransform::ExecutionContext &context,
Chris@27 534 QString configurationXml)
Chris@0 535 {
Chris@27 536 Transform *t = createTransform(name, inputModel, context,
Chris@0 537 configurationXml, false);
Chris@0 538
Chris@0 539 if (!t) return 0;
Chris@0 540
Chris@0 541 connect(t, SIGNAL(finished()), this, SLOT(transformFinished()));
Chris@0 542
Chris@0 543 t->start();
Chris@0 544 return t->detachOutputModel();
Chris@0 545 }
Chris@0 546
Chris@0 547 void
Chris@0 548 TransformFactory::transformFinished()
Chris@0 549 {
Chris@0 550 QObject *s = sender();
Chris@0 551 Transform *transform = dynamic_cast<Transform *>(s);
Chris@0 552
Chris@0 553 if (!transform) {
Chris@0 554 std::cerr << "WARNING: TransformFactory::transformFinished: sender is not a transform" << std::endl;
Chris@0 555 return;
Chris@0 556 }
Chris@0 557
Chris@0 558 transform->wait(); // unnecessary but reassuring
Chris@0 559 delete transform;
Chris@0 560 }
Chris@0 561