# HG changeset patch # User Chris Cannam # Date 1193243878 0 # Node ID 9ea770d93fae0acbd922845e6586ab1f8afc59ca # Parent 9ebe12983f3e71dca46b2db22f16a105f27611b1 * document -> framework (will not compile, path fixes not in yet) diff -r 9ebe12983f3e -r 9ea770d93fae framework/Document.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/framework/Document.cpp Wed Oct 24 16:37:58 2007 +0000 @@ -0,0 +1,949 @@ +/* -*- 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 2006 Chris Cannam and 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 "Document.h" + +#include "data/model/WaveFileModel.h" +#include "data/model/WritableWaveFileModel.h" +#include "data/model/DenseThreeDimensionalModel.h" +#include "data/model/DenseTimeValueModel.h" +#include "layer/Layer.h" +#include "base/CommandHistory.h" +#include "base/Command.h" +#include "view/View.h" +#include "base/PlayParameterRepository.h" +#include "base/PlayParameters.h" +#include "plugin/transform/TransformFactory.h" +#include +#include +#include + +// For alignment: +#include "data/model/AggregateWaveModel.h" +#include "data/model/SparseTimeValueModel.h" +#include "data/model/AlignmentModel.h" + +//!!! still need to handle command history, documentRestored/documentModified + +Document::Document() : + m_mainModel(0) +{ + connect(this, SIGNAL(modelAboutToBeDeleted(Model *)), + TransformFactory::getInstance(), + SLOT(modelAboutToBeDeleted(Model *))); +} + +Document::~Document() +{ + //!!! Document should really own the command history. atm we + //still refer to it in various places that don't have access to + //the document, be nice to fix that + +// std::cerr << "\n\nDocument::~Document: about to clear command history" << std::endl; + CommandHistory::getInstance()->clear(); + +// std::cerr << "Document::~Document: about to delete layers" << std::endl; + while (!m_layers.empty()) { + deleteLayer(*m_layers.begin(), true); + } + + if (!m_models.empty()) { + std::cerr << "Document::~Document: WARNING: " + << m_models.size() << " model(s) still remain -- " + << "should have been garbage collected when deleting layers" + << std::endl; + while (!m_models.empty()) { + Model *model = m_models.begin()->first; + if (model == m_mainModel) { + // just in case! + std::cerr << "Document::~Document: WARNING: Main model is also" + << " in models list!" << std::endl; + } else if (model) { + emit modelAboutToBeDeleted(model); + model->aboutToDelete(); + delete model; + } + m_models.erase(m_models.begin()); + } + } + +// std::cerr << "Document::~Document: About to get rid of main model" +// << std::endl; + if (m_mainModel) { + emit modelAboutToBeDeleted(m_mainModel); + m_mainModel->aboutToDelete(); + } + + emit mainModelChanged(0); + delete m_mainModel; + +} + +Layer * +Document::createLayer(LayerFactory::LayerType type) +{ + Layer *newLayer = LayerFactory::getInstance()->createLayer(type); + if (!newLayer) return 0; + + newLayer->setObjectName(getUniqueLayerName(newLayer->objectName())); + + m_layers.insert(newLayer); + emit layerAdded(newLayer); + + return newLayer; +} + +Layer * +Document::createMainModelLayer(LayerFactory::LayerType type) +{ + Layer *newLayer = createLayer(type); + if (!newLayer) return 0; + setModel(newLayer, m_mainModel); + return newLayer; +} + +Layer * +Document::createImportedLayer(Model *model) +{ + LayerFactory::LayerTypeSet types = + LayerFactory::getInstance()->getValidLayerTypes(model); + + if (types.empty()) { + std::cerr << "WARNING: Document::importLayer: no valid display layer for model" << std::endl; + return 0; + } + + //!!! for now, just use the first suitable layer type + LayerFactory::LayerType type = *types.begin(); + + Layer *newLayer = LayerFactory::getInstance()->createLayer(type); + if (!newLayer) return 0; + + newLayer->setObjectName(getUniqueLayerName(newLayer->objectName())); + + addImportedModel(model); + setModel(newLayer, model); + + //!!! and all channels + setChannel(newLayer, -1); + + m_layers.insert(newLayer); + emit layerAdded(newLayer); + return newLayer; +} + +Layer * +Document::createEmptyLayer(LayerFactory::LayerType type) +{ + Model *newModel = + LayerFactory::getInstance()->createEmptyModel(type, m_mainModel); + if (!newModel) return 0; + + Layer *newLayer = createLayer(type); + if (!newLayer) { + delete newModel; + return 0; + } + + addImportedModel(newModel); + setModel(newLayer, newModel); + + return newLayer; +} + +Layer * +Document::createDerivedLayer(LayerFactory::LayerType type, + TransformId transform) +{ + Layer *newLayer = createLayer(type); + if (!newLayer) return 0; + + newLayer->setObjectName(getUniqueLayerName + (TransformFactory::getInstance()-> + getTransformFriendlyName(transform))); + + return newLayer; +} + +Layer * +Document::createDerivedLayer(TransformId transform, + Model *inputModel, + const PluginTransform::ExecutionContext &context, + QString configurationXml) +{ + Model *newModel = addDerivedModel(transform, inputModel, + context, configurationXml); + if (!newModel) { + // error already printed to stderr by addDerivedModel + emit modelGenerationFailed(transform); + return 0; + } + + LayerFactory::LayerTypeSet types = + LayerFactory::getInstance()->getValidLayerTypes(newModel); + + if (types.empty()) { + std::cerr << "WARNING: Document::createLayerForTransform: no valid display layer for output of transform " << transform.toStdString() << std::endl; + delete newModel; + return 0; + } + + //!!! for now, just use the first suitable layer type + + Layer *newLayer = createLayer(*types.begin()); + setModel(newLayer, newModel); + + //!!! We need to clone the model when adding the layer, so that it + //can be edited without affecting other layers that are based on + //the same model. Unfortunately we can't just clone it now, + //because it probably hasn't been completed yet -- the transform + //runs in the background. Maybe the transform has to handle + //cloning and cacheing models itself. + // + // Once we do clone models here, of course, we'll have to avoid + // leaking them too. + // + // We want the user to be able to add a model to a second layer + // _while it's still being calculated in the first_ and have it + // work quickly. That means we need to put the same physical + // model pointer in both layers, so they can't actually be cloned. + + if (newLayer) { + newLayer->setObjectName(getUniqueLayerName + (TransformFactory::getInstance()-> + getTransformFriendlyName(transform))); + } + + emit layerAdded(newLayer); + return newLayer; +} + +void +Document::setMainModel(WaveFileModel *model) +{ + Model *oldMainModel = m_mainModel; + m_mainModel = model; + + emit modelAdded(m_mainModel); + + std::vector obsoleteLayers; + std::set failedTransforms; + + // We need to ensure that no layer is left using oldMainModel or + // any of the old derived models as its model. Either replace the + // model, or delete the layer for each layer that is currently + // using one of these. Carry out this replacement before we + // delete any of the models. + + for (LayerSet::iterator i = m_layers.begin(); i != m_layers.end(); ++i) { + + Layer *layer = *i; + Model *model = layer->getModel(); + +// std::cerr << "Document::setMainModel: inspecting model " +// << (model ? model->objectName().toStdString() : "(null)") << " in layer " +// << layer->objectName().toStdString() << std::endl; + + if (model == oldMainModel) { +// std::cerr << "... it uses the old main model, replacing" << std::endl; + LayerFactory::getInstance()->setModel(layer, m_mainModel); + continue; + } + + if (m_models.find(model) == m_models.end()) { + std::cerr << "WARNING: Document::setMainModel: Unknown model " + << model << " in layer " << layer << std::endl; + // get rid of this hideous degenerate + obsoleteLayers.push_back(layer); + continue; + } + + if (m_models[model].source == oldMainModel) { + +// std::cerr << "... it uses a model derived from the old main model, regenerating" << std::endl; + + // This model was derived from the previous main + // model: regenerate it. + + TransformId transform = m_models[model].transform; + PluginTransform::ExecutionContext context = m_models[model].context; + + Model *replacementModel = + addDerivedModel(transform, + m_mainModel, + context, + m_models[model].configurationXml); + + if (!replacementModel) { + std::cerr << "WARNING: Document::setMainModel: Failed to regenerate model for transform \"" + << transform.toStdString() << "\"" << " in layer " << layer << std::endl; + if (failedTransforms.find(transform) == failedTransforms.end()) { + emit modelRegenerationFailed(layer->objectName(), + transform); + failedTransforms.insert(transform); + } + obsoleteLayers.push_back(layer); + } else { + std::cerr << "Replacing model " << model << " (type " + << typeid(*model).name() << ") with model " + << replacementModel << " (type " + << typeid(*replacementModel).name() << ") in layer " + << layer << " (name " << layer->objectName().toStdString() << ")" + << std::endl; + RangeSummarisableTimeValueModel *rm = + dynamic_cast(replacementModel); + if (rm) { + std::cerr << "new model has " << rm->getChannelCount() << " channels " << std::endl; + } else { + std::cerr << "new model is not a RangeSummarisableTimeValueModel!" << std::endl; + } + setModel(layer, replacementModel); + } + } + } + + for (size_t k = 0; k < obsoleteLayers.size(); ++k) { + deleteLayer(obsoleteLayers[k], true); + } + + emit mainModelChanged(m_mainModel); + + // we already emitted modelAboutToBeDeleted for this + delete oldMainModel; +} + +void +Document::addDerivedModel(TransformId transform, + Model *inputModel, + const PluginTransform::ExecutionContext &context, + Model *outputModelToAdd, + QString configurationXml) +{ + if (m_models.find(outputModelToAdd) != m_models.end()) { + std::cerr << "WARNING: Document::addDerivedModel: Model already added" + << std::endl; + return; + } + +// std::cerr << "Document::addDerivedModel: source is " << inputModel << " \"" << inputModel->objectName().toStdString() << "\"" << std::endl; + + ModelRecord rec; + rec.source = inputModel; + rec.transform = transform; + rec.context = context; + rec.configurationXml = configurationXml; + rec.refcount = 0; + + outputModelToAdd->setSourceModel(inputModel); + + m_models[outputModelToAdd] = rec; + + emit modelAdded(outputModelToAdd); +} + + +void +Document::addImportedModel(Model *model) +{ + if (m_models.find(model) != m_models.end()) { + std::cerr << "WARNING: Document::addImportedModel: Model already added" + << std::endl; + return; + } + + ModelRecord rec; + rec.source = 0; + rec.transform = ""; + rec.refcount = 0; + + m_models[model] = rec; + + emit modelAdded(model); +} + +Model * +Document::addDerivedModel(TransformId transform, + Model *inputModel, + const PluginTransform::ExecutionContext &context, + QString configurationXml) +{ + Model *model = 0; + + for (ModelMap::iterator i = m_models.begin(); i != m_models.end(); ++i) { + if (i->second.transform == transform && + i->second.source == inputModel && + i->second.context == context && + i->second.configurationXml == configurationXml) { + return i->first; + } + } + + model = TransformFactory::getInstance()->transform + (transform, inputModel, context, configurationXml); + + if (!model) { + std::cerr << "WARNING: Document::addDerivedModel: no output model for transform " << transform.toStdString() << std::endl; + } else { + addDerivedModel(transform, inputModel, context, model, configurationXml); + } + + return model; +} + +void +Document::releaseModel(Model *model) // Will _not_ release main model! +{ + if (model == 0) { + return; + } + + if (model == m_mainModel) { + return; + } + + bool toDelete = false; + + if (m_models.find(model) != m_models.end()) { + + if (m_models[model].refcount == 0) { + std::cerr << "WARNING: Document::releaseModel: model " << model + << " reference count is zero already!" << std::endl; + } else { + if (--m_models[model].refcount == 0) { + toDelete = true; + } + } + } else { + std::cerr << "WARNING: Document::releaseModel: Unfound model " + << model << std::endl; + toDelete = true; + } + + if (toDelete) { + + int sourceCount = 0; + + for (ModelMap::iterator i = m_models.begin(); i != m_models.end(); ++i) { + if (i->second.source == model) { + ++sourceCount; + i->second.source = 0; + } + } + + if (sourceCount > 0) { + std::cerr << "Document::releaseModel: Deleting model " + << model << " even though it is source for " + << sourceCount << " other derived model(s) -- resetting " + << "their source fields appropriately" << std::endl; + } + + emit modelAboutToBeDeleted(model); + model->aboutToDelete(); + m_models.erase(model); + delete model; + } +} + +void +Document::deleteLayer(Layer *layer, bool force) +{ + if (m_layerViewMap.find(layer) != m_layerViewMap.end() && + m_layerViewMap[layer].size() > 0) { + + std::cerr << "WARNING: Document::deleteLayer: Layer " + << layer << " [" << layer->objectName().toStdString() << "]" + << " is still used in " << m_layerViewMap[layer].size() + << " views!" << std::endl; + + if (force) { + + std::cerr << "(force flag set -- deleting from all views)" << std::endl; + + for (std::set::iterator j = m_layerViewMap[layer].begin(); + j != m_layerViewMap[layer].end(); ++j) { + // don't use removeLayerFromView, as it issues a command + layer->setLayerDormant(*j, true); + (*j)->removeLayer(layer); + } + + m_layerViewMap.erase(layer); + + } else { + return; + } + } + + if (m_layers.find(layer) == m_layers.end()) { + std::cerr << "Document::deleteLayer: Layer " + << layer << " does not exist, or has already been deleted " + << "(this may not be as serious as it sounds)" << std::endl; + return; + } + + m_layers.erase(layer); + + releaseModel(layer->getModel()); + emit layerRemoved(layer); + emit layerAboutToBeDeleted(layer); + delete layer; +} + +void +Document::setModel(Layer *layer, Model *model) +{ + if (model && + model != m_mainModel && + m_models.find(model) == m_models.end()) { + std::cerr << "ERROR: Document::setModel: Layer " << layer + << " (\"" << layer->objectName().toStdString() + << "\") wants to use unregistered model " << model + << ": register the layer's model before setting it!" + << std::endl; + return; + } + + Model *previousModel = layer->getModel(); + + if (previousModel == model) { + std::cerr << "WARNING: Document::setModel: Layer " << layer << " (\"" + << layer->objectName().toStdString() + << "\") is already set to model " + << model << " (\"" + << (model ? model->objectName().toStdString() : "(null)") + << "\")" << std::endl; + return; + } + + if (model && model != m_mainModel) { + m_models[model].refcount ++; + } + + if (model && previousModel) { + PlayParameterRepository::getInstance()->copyParameters + (previousModel, model); + } + + LayerFactory::getInstance()->setModel(layer, model); + + if (previousModel) { + releaseModel(previousModel); + } +} + +void +Document::setChannel(Layer *layer, int channel) +{ + LayerFactory::getInstance()->setChannel(layer, channel); +} + +void +Document::addLayerToView(View *view, Layer *layer) +{ + Model *model = layer->getModel(); + if (!model) { +// std::cerr << "Document::addLayerToView: Layer (\"" +// << layer->objectName().toStdString() +// << "\") with no model being added to view: " +// << "normally you want to set the model first" << std::endl; + } else { + if (model != m_mainModel && + m_models.find(model) == m_models.end()) { + std::cerr << "ERROR: Document::addLayerToView: Layer " << layer + << " has unregistered model " << model + << " -- register the layer's model before adding the layer!" << std::endl; + return; + } + } + + CommandHistory::getInstance()->addCommand + (new Document::AddLayerCommand(this, view, layer)); +} + +void +Document::removeLayerFromView(View *view, Layer *layer) +{ + CommandHistory::getInstance()->addCommand + (new Document::RemoveLayerCommand(this, view, layer)); +} + +void +Document::addToLayerViewMap(Layer *layer, View *view) +{ + bool firstView = (m_layerViewMap.find(layer) == m_layerViewMap.end() || + m_layerViewMap[layer].empty()); + + if (m_layerViewMap[layer].find(view) != + m_layerViewMap[layer].end()) { + std::cerr << "WARNING: Document::addToLayerViewMap:" + << " Layer " << layer << " -> view " << view << " already in" + << " layer view map -- internal inconsistency" << std::endl; + } + + m_layerViewMap[layer].insert(view); + + if (firstView) emit layerInAView(layer, true); +} + +void +Document::removeFromLayerViewMap(Layer *layer, View *view) +{ + if (m_layerViewMap[layer].find(view) == + m_layerViewMap[layer].end()) { + std::cerr << "WARNING: Document::removeFromLayerViewMap:" + << " Layer " << layer << " -> view " << view << " not in" + << " layer view map -- internal inconsistency" << std::endl; + } + + m_layerViewMap[layer].erase(view); + + if (m_layerViewMap[layer].empty()) { + m_layerViewMap.erase(layer); + emit layerInAView(layer, false); + } +} + +QString +Document::getUniqueLayerName(QString candidate) +{ + for (int count = 1; ; ++count) { + + QString adjusted = + (count > 1 ? QString("%1 <%2>").arg(candidate).arg(count) : + candidate); + + bool duplicate = false; + + for (LayerSet::iterator i = m_layers.begin(); i != m_layers.end(); ++i) { + if ((*i)->objectName() == adjusted) { + duplicate = true; + break; + } + } + + if (!duplicate) return adjusted; + } +} + +std::vector +Document::getTransformInputModels() +{ + std::vector models; + + if (!m_mainModel) return models; + + models.push_back(m_mainModel); + + //!!! This will pick up all models, including those that aren't visible... + + for (ModelMap::iterator i = m_models.begin(); i != m_models.end(); ++i) { + + Model *model = i->first; + if (!model || model == m_mainModel) continue; + DenseTimeValueModel *dtvm = dynamic_cast(model); + + if (dtvm) { + models.push_back(dtvm); + } + } + + return models; +} + +void +Document::alignModel(Model *model) +{ + if (!m_mainModel || model == m_mainModel) return; + + RangeSummarisableTimeValueModel *rm = + dynamic_cast(model); + if (!rm) return; + + // This involves creating three new models: + + // 1. an AggregateWaveModel to provide the mixdowns of the main + // model and the new model in its two channels, as input to the + // MATCH plugin + + // 2. a SparseTimeValueModel, which is the model automatically + // created by FeatureExtractionPluginTransform when running the + // MATCH plugin (thus containing the alignment path) + + // 3. an AlignmentModel, which stores the path model and carries + // out alignment lookups on it. + + // The first two of these are provided as arguments to the + // constructor for the third, which takes responsibility for + // deleting them. The AlignmentModel, meanwhile, is passed to the + // new model we are aligning, which also takes responsibility for + // it. We should not have to delete any of these new models here. + + AggregateWaveModel::ChannelSpecList components; + + components.push_back(AggregateWaveModel::ModelChannelSpec + (m_mainModel, -1)); + + components.push_back(AggregateWaveModel::ModelChannelSpec + (rm, -1)); + + Model *aggregate = new AggregateWaveModel(components); + + TransformId id = "vamp:match-vamp-plugin:match:path"; + + TransformFactory *factory = TransformFactory::getInstance(); + + Model *transformOutput = factory->transform + (id, aggregate, + factory->getDefaultContextForTransform(id, aggregate), + ""); + + SparseTimeValueModel *path = dynamic_cast + (transformOutput); + + if (!path) { + std::cerr << "Document::alignModel: ERROR: Failed to create alignment path (no MATCH plugin?)" << std::endl; + delete transformOutput; + delete aggregate; + return; + } + + AlignmentModel *alignmentModel = new AlignmentModel + (m_mainModel, model, aggregate, path); + + rm->setAlignment(alignmentModel); +} + +void +Document::alignModels() +{ + for (ModelMap::iterator i = m_models.begin(); i != m_models.end(); ++i) { + alignModel(i->first); + } +} + +Document::AddLayerCommand::AddLayerCommand(Document *d, + View *view, + Layer *layer) : + m_d(d), + m_view(view), + m_layer(layer), + m_name(qApp->translate("AddLayerCommand", "Add %1 Layer").arg(layer->objectName())), + m_added(false) +{ +} + +Document::AddLayerCommand::~AddLayerCommand() +{ +// std::cerr << "Document::AddLayerCommand::~AddLayerCommand" << std::endl; + if (!m_added) { + m_d->deleteLayer(m_layer); + } +} + +void +Document::AddLayerCommand::execute() +{ + for (int i = 0; i < m_view->getLayerCount(); ++i) { + if (m_view->getLayer(i) == m_layer) { + // already there + m_layer->setLayerDormant(m_view, false); + m_added = true; + return; + } + } + + m_view->addLayer(m_layer); + m_layer->setLayerDormant(m_view, false); + + m_d->addToLayerViewMap(m_layer, m_view); + m_added = true; +} + +void +Document::AddLayerCommand::unexecute() +{ + m_view->removeLayer(m_layer); + m_layer->setLayerDormant(m_view, true); + + m_d->removeFromLayerViewMap(m_layer, m_view); + m_added = false; +} + +Document::RemoveLayerCommand::RemoveLayerCommand(Document *d, + View *view, + Layer *layer) : + m_d(d), + m_view(view), + m_layer(layer), + m_name(qApp->translate("RemoveLayerCommand", "Delete %1 Layer").arg(layer->objectName())), + m_added(true) +{ +} + +Document::RemoveLayerCommand::~RemoveLayerCommand() +{ +// std::cerr << "Document::RemoveLayerCommand::~RemoveLayerCommand" << std::endl; + if (!m_added) { + m_d->deleteLayer(m_layer); + } +} + +void +Document::RemoveLayerCommand::execute() +{ + bool have = false; + for (int i = 0; i < m_view->getLayerCount(); ++i) { + if (m_view->getLayer(i) == m_layer) { + have = true; + break; + } + } + + if (!have) { // not there! + m_layer->setLayerDormant(m_view, true); + m_added = false; + return; + } + + m_view->removeLayer(m_layer); + m_layer->setLayerDormant(m_view, true); + + m_d->removeFromLayerViewMap(m_layer, m_view); + m_added = false; +} + +void +Document::RemoveLayerCommand::unexecute() +{ + m_view->addLayer(m_layer); + m_layer->setLayerDormant(m_view, false); + + m_d->addToLayerViewMap(m_layer, m_view); + m_added = true; +} + +void +Document::toXml(QTextStream &out, QString indent, QString extraAttributes) const +{ + out << indent + QString("\n") + .arg(extraAttributes == "" ? "" : " ").arg(extraAttributes); + + if (m_mainModel) { + m_mainModel->toXml(out, indent + " ", "mainModel=\"true\""); + } + + // Models that are not used in a layer that is in a view should + // not be written. Get our list of required models first. + + std::set used; + + for (LayerViewMap::const_iterator i = m_layerViewMap.begin(); + i != m_layerViewMap.end(); ++i) { + + if (i->first && !i->second.empty() && i->first->getModel()) { + used.insert(i->first->getModel()); + } + } + + for (ModelMap::const_iterator i = m_models.begin(); + i != m_models.end(); ++i) { + + const Model *model = i->first; + const ModelRecord &rec = i->second; + + if (used.find(model) == used.end()) continue; + + // We need an intelligent way to determine which models need + // to be streamed (i.e. have been edited, or are small) and + // which should not be (i.e. remain as generated by a + // transform, and are large). + // + // At the moment we can get away with deciding not to stream + // dense 3d models or writable wave file models, provided they + // were generated from a transform, because at the moment there + // is no way to edit those model types so it should be safe to + // regenerate them. That won't always work in future though. + // It would be particularly nice to be able to ask the user, + // as well as making an intelligent guess. + + bool writeModel = true; + bool haveDerivation = false; + + if (rec.source && rec.transform != "") { + haveDerivation = true; + } + + if (haveDerivation) { + if (dynamic_cast(model)) { + writeModel = false; + } else if (dynamic_cast(model)) { + writeModel = false; + } + } + + if (writeModel) { + i->first->toXml(out, indent + " "); + } + + if (haveDerivation) { + + QString extentsAttributes; + if (rec.context.startFrame != 0 || + rec.context.duration != 0) { + extentsAttributes = QString("startFrame=\"%1\" duration=\"%2\" ") + .arg(rec.context.startFrame) + .arg(rec.context.duration); + } + + out << indent; + out << QString(" first)) + .arg(rec.context.channel) + .arg(rec.context.domain) + .arg(rec.context.stepSize) + .arg(rec.context.blockSize) + .arg(extentsAttributes) + .arg(int(rec.context.windowType)) + .arg(XmlExportable::encodeEntities(rec.transform)); + + if (rec.configurationXml != "") { + out << ">\n " + indent + rec.configurationXml + + "\n" + indent + " \n"; + } else { + out << "/>\n"; + } + } + + //!!! We should probably own the PlayParameterRepository + PlayParameters *playParameters = + PlayParameterRepository::getInstance()->getPlayParameters(i->first); + if (playParameters) { + playParameters->toXml + (out, indent + " ", + QString("model=\"%1\"") + .arg(XmlExportable::getObjectExportId(i->first))); + } + } + + for (LayerSet::const_iterator i = m_layers.begin(); + i != m_layers.end(); ++i) { + + (*i)->toXml(out, indent + " "); + } + + out << indent + "\n"; +} + + diff -r 9ebe12983f3e -r 9ea770d93fae framework/Document.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/framework/Document.h Wed Oct 24 16:37:58 2007 +0000 @@ -0,0 +1,316 @@ +/* -*- 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 2006 Chris Cannam and 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 _DOCUMENT_H_ +#define _DOCUMENT_H_ + +#include "layer/LayerFactory.h" +#include "plugin/transform/Transform.h" +#include "plugin/transform/PluginTransform.h" +#include "base/Command.h" + +#include +#include + +class Model; +class Layer; +class View; +class WaveFileModel; + +/** + * A Sonic Visualiser document consists of a set of data models, and + * also the visualisation layers used to display them. Changes to the + * layers and their layout need to be stored and managed in much the + * same way as changes to the underlying data. + * + * The document manages: + * + * - A main data Model, which provides the underlying sample rate and + * such like. This must be a WaveFileModel. + * + * - Any number of imported Model objects, which contain data without any + * requirement to remember where the data came from or how to + * regenerate it. + * + * - Any number of Model objects that were generated by a Transform + * such as FeatureExtractionPluginTransform. For these, we also + * record the source model and the name of the transform used to + * generate the model so that we can regenerate it (potentially + * from a different source) on demand. + * + * - A flat list of Layer objects. Elsewhere, the GUI may distribute these + * across any number of View widgets. A layer may be viewable on more + * than one view at once, in principle. A layer refers to one model, + * but the same model can be in use in more than one layer. + * + * The document does *not* manage the existence or structure of Pane + * and other view widgets. However, it does provide convenience + * methods for reference-counted command-based management of the + * association between layers and views (addLayerToView, + * removeLayerFromView). + */ + +class Document : public QObject, + public XmlExportable +{ + Q_OBJECT + +public: + Document(); + virtual ~Document(); + + /** + * Create and return a new layer of the given type, associated + * with no model. The caller may set any model on this layer, but + * the model must also be registered with the document via the + * add-model methods below. + */ + Layer *createLayer(LayerFactory::LayerType); + + /** + * Create and return a new layer of the given type, associated + * with the current main model (if appropriate to the layer type). + */ + Layer *createMainModelLayer(LayerFactory::LayerType); + + /** + * Create and return a new layer associated with the given model, + * and register the model as an imported model. + */ + Layer *createImportedLayer(Model *); + + /** + * Create and return a new layer of the given type, with an + * appropriate empty model. If the given type is not one for + * which an empty model can meaningfully be created, return 0. + */ + Layer *createEmptyLayer(LayerFactory::LayerType); + + /** + * Create and return a new layer of the given type, associated + * with the given transform name. This method does not run the + * transform itself, nor create a model. The caller can safely + * add a model to the layer later, but note that all models used + * by a transform layer _must_ be registered with the document + * using addDerivedModel below. + */ + Layer *createDerivedLayer(LayerFactory::LayerType, TransformId); + + /** + * Create and return a suitable layer for the given transform, + * running the transform and associating the resulting model with + * the new layer. + */ + Layer *createDerivedLayer(TransformId, + Model *inputModel, + const PluginTransform::ExecutionContext &context, + QString configurationXml); + + /** + * Set the main model (the source for playback sample rate, etc) + * to the given wave file model. This will regenerate any derived + * models that were based on the previous main model. + */ + void setMainModel(WaveFileModel *); + + /** + * Get the main model (the source for playback sample rate, etc). + */ + WaveFileModel *getMainModel() { return m_mainModel; } + + /** + * Get the main model (the source for playback sample rate, etc). + */ + const WaveFileModel *getMainModel() const { return m_mainModel; } + + std::vector getTransformInputModels(); + + /** + * Add a derived model associated with the given transform, + * running the transform and returning the resulting model. + */ + Model *addDerivedModel(TransformId transform, + Model *inputModel, + const PluginTransform::ExecutionContext &context, + QString configurationXml); + + /** + * Add a derived model associated with the given transform. This + * is necessary to register any derived model that was not created + * by the document using createDerivedModel or createDerivedLayer. + */ + void addDerivedModel(TransformId, + Model *inputModel, + const PluginTransform::ExecutionContext &context, + Model *outputModelToAdd, + QString configurationXml); + + /** + * Add an imported (non-derived, non-main) model. This is + * necessary to register any imported model that is associated + * with a layer. + */ + void addImportedModel(Model *); + + /** + * Associate the given model with the given layer. The model must + * have already been registered using one of the addXXModel + * methods above. + */ + void setModel(Layer *, Model *); + + /** + * Set the given layer to use the given channel of its model (-1 + * means all available channels). + */ + void setChannel(Layer *, int); + + /** + * Add the given layer to the given view. If the layer is + * intended to show a particular model, the model should normally + * be set using setModel before this method is called. + */ + void addLayerToView(View *, Layer *); + + /** + * Remove the given layer from the given view. + */ + void removeLayerFromView(View *, Layer *); + + void toXml(QTextStream &, QString indent, QString extraAttributes) const; +signals: + void layerAdded(Layer *); + void layerRemoved(Layer *); + void layerAboutToBeDeleted(Layer *); + + // Emitted when a layer is first added to a view, or when it is + // last removed from a view + void layerInAView(Layer *, bool); + + void modelAdded(Model *); + void mainModelChanged(WaveFileModel *); // emitted after modelAdded + void modelAboutToBeDeleted(Model *); + + void modelGenerationFailed(QString transformName); + void modelRegenerationFailed(QString layerName, QString transformName); + +protected: + void releaseModel(Model *model); + + /** + * Delete the given layer, and also its associated model if no + * longer used by any other layer. In general, this should be the + * only method used to delete layers -- doing so directly is a bit + * of a social gaffe. + */ + void deleteLayer(Layer *, bool force = false); + + /** + * If model is suitable for alignment, align it against the main + * model and store the alignment in the model. + */ + void alignModel(Model *); + + /** + * Realign all models if the main model has changed. Is this wise? + */ + void alignModels(); + + /* + * Every model that is in use by a layer in the document must be + * found in either m_mainModel or m_models. We own and control + * the lifespan of all of these models. + */ + + /** + * The model that provides the underlying sample rate, etc. This + * model is not reference counted for layers, and is not freed + * unless it is replaced or the document is deleted. + */ + WaveFileModel *m_mainModel; + + struct ModelRecord + { + // Information associated with a non-main model. If this + // model is derived from another, then source will be non-NULL + // and the transform name will be set appropriately. If the + // transform name is set but source is NULL, then there was a + // transform involved but the (target) model has been modified + // since being generated from it. + const Model *source; + TransformId transform; + PluginTransform::ExecutionContext context; + QString configurationXml; + + // Count of the number of layers using this model. + int refcount; + }; + + typedef std::map ModelMap; + ModelMap m_models; + + class AddLayerCommand : public Command + { + public: + AddLayerCommand(Document *d, View *view, Layer *layer); + virtual ~AddLayerCommand(); + + virtual void execute(); + virtual void unexecute(); + virtual QString getName() const { return m_name; } + + protected: + Document *m_d; + View *m_view; // I don't own this + Layer *m_layer; // Document owns this, but I determine its lifespans + QString m_name; + bool m_added; + }; + + class RemoveLayerCommand : public Command + { + public: + RemoveLayerCommand(Document *d, View *view, Layer *layer); + virtual ~RemoveLayerCommand(); + + virtual void execute(); + virtual void unexecute(); + virtual QString getName() const { return m_name; } + + protected: + Document *m_d; + View *m_view; // I don't own this + Layer *m_layer; // Document owns this, but I determine its lifespan + QString m_name; + bool m_added; + }; + + typedef std::map > LayerViewMap; + LayerViewMap m_layerViewMap; + + void addToLayerViewMap(Layer *, View *); + void removeFromLayerViewMap(Layer *, View *); + + QString getUniqueLayerName(QString candidate); + + /** + * And these are the layers. We also control the lifespans of + * these (usually through the commands used to add and remove them). + */ + typedef std::set LayerSet; + LayerSet m_layers; +}; + +#endif diff -r 9ebe12983f3e -r 9ea770d93fae framework/MainWindowBase.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/framework/MainWindowBase.cpp Wed Oct 24 16:37:58 2007 +0000 @@ -0,0 +1,2006 @@ +/* -*- 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 2006-2007 Chris Cannam and 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 "MainWindowBase.h" +#include "document/Document.h" + + +#include "view/Pane.h" +#include "view/PaneStack.h" +#include "data/model/WaveFileModel.h" +#include "data/model/SparseOneDimensionalModel.h" +#include "data/model/NoteModel.h" +#include "data/model/Labeller.h" +#include "view/ViewManager.h" + +#include "layer/WaveformLayer.h" +#include "layer/TimeRulerLayer.h" +#include "layer/TimeInstantLayer.h" +#include "layer/TimeValueLayer.h" +#include "layer/Colour3DPlotLayer.h" +#include "layer/SliceLayer.h" +#include "layer/SliceableLayer.h" +#include "layer/ImageLayer.h" + +#include "widgets/ListInputDialog.h" + +#include "audioio/AudioCallbackPlaySource.h" +#include "audioio/AudioCallbackPlayTarget.h" +#include "audioio/AudioTargetFactory.h" +#include "audioio/PlaySpeedRangeMapper.h" +#include "data/fileio/DataFileReaderFactory.h" +#include "data/fileio/PlaylistFileReader.h" +#include "data/fileio/WavFileWriter.h" +#include "data/fileio/CSVFileWriter.h" +#include "data/fileio/MIDIFileWriter.h" +#include "data/fileio/BZipFileDevice.h" +#include "data/fileio/FileSource.h" + +#include "data/fft/FFTDataServer.h" + +#include "base/RecentFiles.h" + +#include "base/PlayParameterRepository.h" +#include "base/XmlExportable.h" +#include "base/CommandHistory.h" +#include "base/Profiler.h" +#include "base/Preferences.h" + +#include "data/osc/OSCQueue.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +using std::cerr; +using std::endl; + +using std::vector; +using std::map; +using std::set; + + +MainWindowBase::MainWindowBase(bool withAudioOutput, bool withOSCSupport) : + m_document(0), + m_paneStack(0), + m_viewManager(0), + m_timeRulerLayer(0), + m_audioOutput(withAudioOutput), + m_playSource(0), + m_playTarget(0), + m_oscQueue(withOSCSupport ? new OSCQueue() : 0), + m_recentFiles("RecentFiles", 20), + m_recentTransforms("RecentTransforms", 20), + m_documentModified(false), + m_openingAudioFile(false), + m_abandoning(false), + m_labeller(0) +{ + connect(CommandHistory::getInstance(), SIGNAL(commandExecuted()), + this, SLOT(documentModified())); + connect(CommandHistory::getInstance(), SIGNAL(documentRestored()), + this, SLOT(documentRestored())); + + m_viewManager = new ViewManager(); + connect(m_viewManager, SIGNAL(selectionChanged()), + this, SLOT(updateMenuStates())); + connect(m_viewManager, SIGNAL(inProgressSelectionChanged()), + this, SLOT(inProgressSelectionChanged())); + + Preferences::BackgroundMode mode = + Preferences::getInstance()->getBackgroundMode(); + m_initialDarkBackground = m_viewManager->getGlobalDarkBackground(); + if (mode != Preferences::BackgroundFromTheme) { + m_viewManager->setGlobalDarkBackground + (mode == Preferences::DarkBackground); + } + + m_paneStack = new PaneStack(0, m_viewManager); + connect(m_paneStack, SIGNAL(currentPaneChanged(Pane *)), + this, SLOT(currentPaneChanged(Pane *))); + connect(m_paneStack, SIGNAL(currentLayerChanged(Pane *, Layer *)), + this, SLOT(currentLayerChanged(Pane *, Layer *))); + connect(m_paneStack, SIGNAL(rightButtonMenuRequested(Pane *, QPoint)), + this, SLOT(rightButtonMenuRequested(Pane *, QPoint))); + connect(m_paneStack, SIGNAL(contextHelpChanged(const QString &)), + this, SLOT(contextHelpChanged(const QString &))); + connect(m_paneStack, SIGNAL(paneAdded(Pane *)), + this, SLOT(paneAdded(Pane *))); + connect(m_paneStack, SIGNAL(paneHidden(Pane *)), + this, SLOT(paneHidden(Pane *))); + connect(m_paneStack, SIGNAL(paneAboutToBeDeleted(Pane *)), + this, SLOT(paneAboutToBeDeleted(Pane *))); + connect(m_paneStack, SIGNAL(dropAccepted(Pane *, QStringList)), + this, SLOT(paneDropAccepted(Pane *, QStringList))); + connect(m_paneStack, SIGNAL(dropAccepted(Pane *, QString)), + this, SLOT(paneDropAccepted(Pane *, QString))); + + m_playSource = new AudioCallbackPlaySource(m_viewManager); + + connect(m_playSource, SIGNAL(sampleRateMismatch(size_t, size_t, bool)), + this, SLOT(sampleRateMismatch(size_t, size_t, bool))); + connect(m_playSource, SIGNAL(audioOverloadPluginDisabled()), + this, SLOT(audioOverloadPluginDisabled())); + + connect(m_viewManager, SIGNAL(outputLevelsChanged(float, float)), + this, SLOT(outputLevelsChanged(float, float))); + + connect(m_viewManager, SIGNAL(playbackFrameChanged(unsigned long)), + this, SLOT(playbackFrameChanged(unsigned long))); + + connect(m_viewManager, SIGNAL(globalCentreFrameChanged(unsigned long)), + this, SLOT(globalCentreFrameChanged(unsigned long))); + + connect(m_viewManager, SIGNAL(viewCentreFrameChanged(View *, unsigned long)), + this, SLOT(viewCentreFrameChanged(View *, unsigned long))); + + connect(m_viewManager, SIGNAL(viewZoomLevelChanged(View *, unsigned long, bool)), + this, SLOT(viewZoomLevelChanged(View *, unsigned long, bool))); + + connect(Preferences::getInstance(), + SIGNAL(propertyChanged(PropertyContainer::PropertyName)), + this, + SLOT(preferenceChanged(PropertyContainer::PropertyName))); + + if (m_oscQueue && m_oscQueue->isOK()) { + connect(m_oscQueue, SIGNAL(messagesAvailable()), this, SLOT(pollOSC())); + QTimer *oscTimer = new QTimer(this); + connect(oscTimer, SIGNAL(timeout()), this, SLOT(pollOSC())); + oscTimer->start(1000); + } + + Labeller::ValueType labellerType = Labeller::ValueFromTwoLevelCounter; + QSettings settings; + settings.beginGroup("MainWindow"); + labellerType = (Labeller::ValueType) + settings.value("labellertype", (int)labellerType).toInt(); + int cycle = settings.value("labellercycle", 4).toInt(); + settings.endGroup(); + + m_labeller = new Labeller(labellerType); + m_labeller->setCounterCycleSize(cycle); +} + +MainWindowBase::~MainWindowBase() +{ + delete m_playTarget; + delete m_playSource; + delete m_viewManager; + delete m_oscQueue; + Profiles::getInstance()->dump(); +} + +QString +MainWindowBase::getOpenFileName(FileFinder::FileType type) +{ + FileFinder *ff = FileFinder::getInstance(); + switch (type) { + case FileFinder::SessionFile: + return ff->getOpenFileName(type, m_sessionFile); + case FileFinder::AudioFile: + return ff->getOpenFileName(type, m_audioFile); + case FileFinder::LayerFile: + return ff->getOpenFileName(type, m_sessionFile); + case FileFinder::LayerFileNoMidi: + return ff->getOpenFileName(type, m_sessionFile); + case FileFinder::SessionOrAudioFile: + return ff->getOpenFileName(type, m_sessionFile); + case FileFinder::ImageFile: + return ff->getOpenFileName(type, m_sessionFile); + case FileFinder::AnyFile: + if (getMainModel() != 0 && + m_paneStack != 0 && + m_paneStack->getCurrentPane() != 0) { // can import a layer + return ff->getOpenFileName(FileFinder::AnyFile, m_sessionFile); + } else { + return ff->getOpenFileName(FileFinder::SessionOrAudioFile, + m_sessionFile); + } + } + return ""; +} + +QString +MainWindowBase::getSaveFileName(FileFinder::FileType type) +{ + FileFinder *ff = FileFinder::getInstance(); + switch (type) { + case FileFinder::SessionFile: + return ff->getSaveFileName(type, m_sessionFile); + case FileFinder::AudioFile: + return ff->getSaveFileName(type, m_audioFile); + case FileFinder::LayerFile: + return ff->getSaveFileName(type, m_sessionFile); + case FileFinder::LayerFileNoMidi: + return ff->getSaveFileName(type, m_sessionFile); + case FileFinder::SessionOrAudioFile: + return ff->getSaveFileName(type, m_sessionFile); + case FileFinder::ImageFile: + return ff->getSaveFileName(type, m_sessionFile); + case FileFinder::AnyFile: + return ff->getSaveFileName(type, m_sessionFile); + } + return ""; +} + +void +MainWindowBase::registerLastOpenedFilePath(FileFinder::FileType type, QString path) +{ + FileFinder *ff = FileFinder::getInstance(); + ff->registerLastOpenedFilePath(type, path); +} + +void +MainWindowBase::updateMenuStates() +{ + Pane *currentPane = 0; + Layer *currentLayer = 0; + + if (m_paneStack) currentPane = m_paneStack->getCurrentPane(); + if (currentPane) currentLayer = currentPane->getSelectedLayer(); + + bool haveCurrentPane = + (currentPane != 0); + bool haveCurrentLayer = + (haveCurrentPane && + (currentLayer != 0)); + bool haveMainModel = + (getMainModel() != 0); + bool havePlayTarget = + (m_playTarget != 0); + bool haveSelection = + (m_viewManager && + !m_viewManager->getSelections().empty()); + bool haveCurrentEditableLayer = + (haveCurrentLayer && + currentLayer->isLayerEditable()); + bool haveCurrentTimeInstantsLayer = + (haveCurrentLayer && + dynamic_cast(currentLayer)); + bool haveCurrentColour3DPlot = + (haveCurrentLayer && + dynamic_cast(currentLayer)); + bool haveClipboardContents = + (m_viewManager && + !m_viewManager->getClipboard().empty()); + + emit canAddPane(haveMainModel); + emit canDeleteCurrentPane(haveCurrentPane); + emit canZoom(haveMainModel && haveCurrentPane); + emit canScroll(haveMainModel && haveCurrentPane); + emit canAddLayer(haveMainModel && haveCurrentPane); + emit canImportMoreAudio(haveMainModel); + emit canImportLayer(haveMainModel && haveCurrentPane); + emit canExportAudio(haveMainModel); + emit canExportLayer(haveMainModel && + (haveCurrentEditableLayer || haveCurrentColour3DPlot)); + emit canExportImage(haveMainModel && haveCurrentPane); + emit canDeleteCurrentLayer(haveCurrentLayer); + emit canRenameLayer(haveCurrentLayer); + emit canEditLayer(haveCurrentEditableLayer); + emit canMeasureLayer(haveCurrentLayer); + emit canSelect(haveMainModel && haveCurrentPane); + emit canPlay(havePlayTarget); + emit canFfwd(true); + emit canRewind(true); + emit canPaste(haveCurrentEditableLayer && haveClipboardContents); + emit canInsertInstant(haveCurrentPane); + emit canInsertInstantsAtBoundaries(haveCurrentPane && haveSelection); + emit canRenumberInstants(haveCurrentTimeInstantsLayer && haveSelection); + emit canPlaySelection(haveMainModel && havePlayTarget && haveSelection); + emit canClearSelection(haveSelection); + emit canEditSelection(haveSelection && haveCurrentEditableLayer); + emit canSave(m_sessionFile != "" && m_documentModified); +} + +void +MainWindowBase::documentModified() +{ +// std::cerr << "MainWindowBase::documentModified" << std::endl; + + if (!m_documentModified) { + //!!! this in subclass implementation? + setWindowTitle(tr("%1 (modified)").arg(windowTitle())); + } + + m_documentModified = true; + updateMenuStates(); +} + +void +MainWindowBase::documentRestored() +{ +// std::cerr << "MainWindowBase::documentRestored" << std::endl; + + if (m_documentModified) { + //!!! this in subclass implementation? + QString wt(windowTitle()); + wt.replace(tr(" (modified)"), ""); + setWindowTitle(wt); + } + + m_documentModified = false; + updateMenuStates(); +} + +void +MainWindowBase::playLoopToggled() +{ + QAction *action = dynamic_cast(sender()); + + if (action) { + m_viewManager->setPlayLoopMode(action->isChecked()); + } else { + m_viewManager->setPlayLoopMode(!m_viewManager->getPlayLoopMode()); + } +} + +void +MainWindowBase::playSelectionToggled() +{ + QAction *action = dynamic_cast(sender()); + + if (action) { + m_viewManager->setPlaySelectionMode(action->isChecked()); + } else { + m_viewManager->setPlaySelectionMode(!m_viewManager->getPlaySelectionMode()); + } +} + +void +MainWindowBase::playSoloToggled() +{ + QAction *action = dynamic_cast(sender()); + + if (action) { + m_viewManager->setPlaySoloMode(action->isChecked()); + } else { + m_viewManager->setPlaySoloMode(!m_viewManager->getPlaySoloMode()); + } + + if (m_viewManager->getPlaySoloMode()) { + currentPaneChanged(m_paneStack->getCurrentPane()); + } else { + m_viewManager->setPlaybackModel(0); + if (m_playSource) { + m_playSource->clearSoloModelSet(); + } + } +} + +void +MainWindowBase::currentPaneChanged(Pane *p) +{ + updateMenuStates(); + updateVisibleRangeDisplay(p); + + if (!p) return; + + if (!(m_viewManager && + m_playSource && + m_viewManager->getPlaySoloMode())) { + if (m_viewManager) m_viewManager->setPlaybackModel(0); + return; + } + + Model *prevPlaybackModel = m_viewManager->getPlaybackModel(); + + View::ModelSet soloModels = p->getModels(); + + for (View::ModelSet::iterator mi = soloModels.begin(); + mi != soloModels.end(); ++mi) { + if (dynamic_cast(*mi)) { + m_viewManager->setPlaybackModel(*mi); + } + } + + RangeSummarisableTimeValueModel *a = + dynamic_cast(prevPlaybackModel); + RangeSummarisableTimeValueModel *b = + dynamic_cast(m_viewManager-> + getPlaybackModel()); + + m_playSource->setSoloModelSet(soloModels); + + if (a && b && (a != b)) { + int frame = m_playSource->getCurrentPlayingFrame(); + //!!! I don't really believe that these functions are the right way around + int rframe = a->alignFromReference(frame); + int bframe = b->alignToReference(rframe); + if (m_playSource->isPlaying()) m_playSource->play(bframe); + } +} + +void +MainWindowBase::currentLayerChanged(Pane *p, Layer *) +{ + updateMenuStates(); + updateVisibleRangeDisplay(p); +} + +void +MainWindowBase::selectAll() +{ + if (!getMainModel()) return; + m_viewManager->setSelection(Selection(getMainModel()->getStartFrame(), + getMainModel()->getEndFrame())); +} + +void +MainWindowBase::selectToStart() +{ + if (!getMainModel()) return; + m_viewManager->setSelection(Selection(getMainModel()->getStartFrame(), + m_viewManager->getGlobalCentreFrame())); +} + +void +MainWindowBase::selectToEnd() +{ + if (!getMainModel()) return; + m_viewManager->setSelection(Selection(m_viewManager->getGlobalCentreFrame(), + getMainModel()->getEndFrame())); +} + +void +MainWindowBase::selectVisible() +{ + Model *model = getMainModel(); + if (!model) return; + + Pane *currentPane = m_paneStack->getCurrentPane(); + if (!currentPane) return; + + size_t startFrame, endFrame; + + if (currentPane->getStartFrame() < 0) startFrame = 0; + else startFrame = currentPane->getStartFrame(); + + if (currentPane->getEndFrame() > model->getEndFrame()) endFrame = model->getEndFrame(); + else endFrame = currentPane->getEndFrame(); + + m_viewManager->setSelection(Selection(startFrame, endFrame)); +} + +void +MainWindowBase::clearSelection() +{ + m_viewManager->clearSelections(); +} + +void +MainWindowBase::cut() +{ + Pane *currentPane = m_paneStack->getCurrentPane(); + if (!currentPane) return; + + Layer *layer = currentPane->getSelectedLayer(); + if (!layer) return; + + Clipboard &clipboard = m_viewManager->getClipboard(); + clipboard.clear(); + + MultiSelection::SelectionList selections = m_viewManager->getSelections(); + + CommandHistory::getInstance()->startCompoundOperation(tr("Cut"), true); + + for (MultiSelection::SelectionList::iterator i = selections.begin(); + i != selections.end(); ++i) { + layer->copy(*i, clipboard); + layer->deleteSelection(*i); + } + + CommandHistory::getInstance()->endCompoundOperation(); +} + +void +MainWindowBase::copy() +{ + Pane *currentPane = m_paneStack->getCurrentPane(); + if (!currentPane) return; + + Layer *layer = currentPane->getSelectedLayer(); + if (!layer) return; + + Clipboard &clipboard = m_viewManager->getClipboard(); + clipboard.clear(); + + MultiSelection::SelectionList selections = m_viewManager->getSelections(); + + for (MultiSelection::SelectionList::iterator i = selections.begin(); + i != selections.end(); ++i) { + layer->copy(*i, clipboard); + } +} + +void +MainWindowBase::paste() +{ + Pane *currentPane = m_paneStack->getCurrentPane(); + if (!currentPane) return; + + //!!! if we have no current layer, we should create one of the most + // appropriate type + + Layer *layer = currentPane->getSelectedLayer(); + if (!layer) return; + + Clipboard &clipboard = m_viewManager->getClipboard(); + Clipboard::PointList contents = clipboard.getPoints(); +/* + long minFrame = 0; + bool have = false; + for (int i = 0; i < contents.size(); ++i) { + if (!contents[i].haveFrame()) continue; + if (!have || contents[i].getFrame() < minFrame) { + minFrame = contents[i].getFrame(); + have = true; + } + } + + long frameOffset = long(m_viewManager->getGlobalCentreFrame()) - minFrame; + + layer->paste(clipboard, frameOffset); +*/ + layer->paste(clipboard, 0, true); +} + +void +MainWindowBase::deleteSelected() +{ + if (m_paneStack->getCurrentPane() && + m_paneStack->getCurrentPane()->getSelectedLayer()) { + + Layer *layer = m_paneStack->getCurrentPane()->getSelectedLayer(); + + if (m_viewManager && + (m_viewManager->getToolMode() == ViewManager::MeasureMode)) { + + layer->deleteCurrentMeasureRect(); + + } else { + + MultiSelection::SelectionList selections = + m_viewManager->getSelections(); + + for (MultiSelection::SelectionList::iterator i = selections.begin(); + i != selections.end(); ++i) { + layer->deleteSelection(*i); + } + } + } +} + +void +MainWindowBase::insertInstant() +{ + int frame = m_viewManager->getPlaybackFrame(); + insertInstantAt(frame); +} + +void +MainWindowBase::insertInstantsAtBoundaries() +{ + MultiSelection::SelectionList selections = m_viewManager->getSelections(); + for (MultiSelection::SelectionList::iterator i = selections.begin(); + i != selections.end(); ++i) { + size_t start = i->getStartFrame(); + size_t end = i->getEndFrame(); + if (start != end) { + insertInstantAt(i->getStartFrame()); + insertInstantAt(i->getEndFrame()); + } + } +} + +void +MainWindowBase::insertInstantAt(size_t frame) +{ + Pane *pane = m_paneStack->getCurrentPane(); + if (!pane) { + return; + } + + Layer *layer = dynamic_cast + (pane->getSelectedLayer()); + + if (!layer) { + for (int i = pane->getLayerCount(); i > 0; --i) { + layer = dynamic_cast(pane->getLayer(i - 1)); + if (layer) break; + } + + if (!layer) { + CommandHistory::getInstance()->startCompoundOperation + (tr("Add Point"), true); + layer = m_document->createEmptyLayer(LayerFactory::TimeInstants); + if (layer) { + m_document->addLayerToView(pane, layer); + m_paneStack->setCurrentLayer(pane, layer); + } + CommandHistory::getInstance()->endCompoundOperation(); + } + } + + if (layer) { + + Model *model = layer->getModel(); + SparseOneDimensionalModel *sodm = dynamic_cast + (model); + + if (sodm) { + SparseOneDimensionalModel::Point point(frame, ""); + + SparseOneDimensionalModel::Point prevPoint(0); + bool havePrevPoint = false; + + SparseOneDimensionalModel::EditCommand *command = + new SparseOneDimensionalModel::EditCommand(sodm, tr("Add Point")); + + if (m_labeller->actingOnPrevPoint()) { + + SparseOneDimensionalModel::PointList prevPoints = + sodm->getPreviousPoints(frame); + + if (!prevPoints.empty()) { + prevPoint = *prevPoints.begin(); + havePrevPoint = true; + } + } + + if (m_labeller) { + + m_labeller->setSampleRate(sodm->getSampleRate()); + + if (havePrevPoint) { + command->deletePoint(prevPoint); + } + + m_labeller->label + (point, havePrevPoint ? &prevPoint : 0); + + if (havePrevPoint) { + command->addPoint(prevPoint); + } + } + + command->addPoint(point); + + command->setName(tr("Add Point at %1 s") + .arg(RealTime::frame2RealTime + (frame, + sodm->getSampleRate()) + .toText(false).c_str())); + + command->finish(); + } + } +} + +void +MainWindowBase::renumberInstants() +{ + Pane *pane = m_paneStack->getCurrentPane(); + if (!pane) return; + + Layer *layer = dynamic_cast(pane->getSelectedLayer()); + if (!layer) return; + + MultiSelection ms(m_viewManager->getSelection()); + + Model *model = layer->getModel(); + SparseOneDimensionalModel *sodm = dynamic_cast + (model); + if (!sodm) return; + + if (!m_labeller) return; + + Labeller labeller(*m_labeller); + labeller.setSampleRate(sodm->getSampleRate()); + + // This uses a command + + labeller.labelAll(*sodm, &ms); +} + +MainWindowBase::FileOpenStatus +MainWindowBase::open(QString fileOrUrl, AudioFileOpenMode mode) +{ + return open(FileSource(fileOrUrl), mode); +} + +MainWindowBase::FileOpenStatus +MainWindowBase::open(FileSource source, AudioFileOpenMode mode) +{ + FileOpenStatus status; + + if (!source.isAvailable()) return FileOpenFailed; + source.waitForData(); + + bool canImportLayer = (getMainModel() != 0 && + m_paneStack != 0 && + m_paneStack->getCurrentPane() != 0); + + if ((status = openAudio(source, mode)) != FileOpenFailed) { + return status; + } else if ((status = openSession(source)) != FileOpenFailed) { + return status; + } else if ((status = openPlaylist(source, mode)) != FileOpenFailed) { + return status; + } else if (!canImportLayer) { + return FileOpenWrongMode; + } else if ((status = openImage(source)) != FileOpenFailed) { + return status; + } else if ((status = openLayer(source)) != FileOpenFailed) { + return status; + } else { + return FileOpenFailed; + } +} + +MainWindowBase::FileOpenStatus +MainWindowBase::openAudio(FileSource source, AudioFileOpenMode mode) +{ + std::cerr << "MainWindowBase::openAudio(" << source.getLocation().toStdString() << ")" << std::endl; + + if (!source.isAvailable()) return FileOpenFailed; + source.waitForData(); + + m_openingAudioFile = true; + + size_t rate = 0; + + if (Preferences::getInstance()->getResampleOnLoad()) { + rate = m_playSource->getSourceSampleRate(); + } + + WaveFileModel *newModel = new WaveFileModel(source, rate); + + if (!newModel->isOK()) { + delete newModel; + m_openingAudioFile = false; + return FileOpenFailed; + } + + std::cerr << "mode = " << mode << std::endl; + + if (mode == AskUser) { + if (getMainModel()) { + + static bool prevSetAsMain = true; + bool setAsMain = true; + + QStringList items; + items << tr("Replace the existing main waveform") + << tr("Load this file into a new waveform pane"); + + bool ok = false; + QString item = ListInputDialog::getItem + (this, tr("Select target for import"), + tr("You already have an audio waveform loaded.\nWhat would you like to do with the new audio file?"), + items, prevSetAsMain ? 0 : 1, &ok); + + if (!ok || item.isEmpty()) { + delete newModel; + m_openingAudioFile = false; + return FileOpenCancelled; + } + + setAsMain = (item == items[0]); + prevSetAsMain = setAsMain; + + if (setAsMain) mode = ReplaceMainModel; + else mode = CreateAdditionalModel; + + } else { + mode = ReplaceMainModel; + } + } + + if (mode == ReplaceCurrentPane) { + + Pane *pane = m_paneStack->getCurrentPane(); + if (pane) { + if (getMainModel()) { + View::ModelSet models(pane->getModels()); + if (models.find(getMainModel()) != models.end()) { + mode = ReplaceMainModel; + } + } else { + mode = ReplaceMainModel; + } + } else { + mode = CreateAdditionalModel; + } + } + + if (mode == CreateAdditionalModel && !getMainModel()) { + mode = ReplaceMainModel; + } + + if (mode == ReplaceMainModel) { + + Model *prevMain = getMainModel(); + if (prevMain) { + m_playSource->removeModel(prevMain); + PlayParameterRepository::getInstance()->removeModel(prevMain); + } + PlayParameterRepository::getInstance()->addModel(newModel); + + m_document->setMainModel(newModel); + + setupMenus(); + + if (m_sessionFile == "") { + //!!! shouldn't be dealing directly with title from here -- call a method + setWindowTitle(tr("Sonic Visualiser: %1") + .arg(source.getLocation())); + CommandHistory::getInstance()->clear(); + CommandHistory::getInstance()->documentSaved(); + m_documentModified = false; + } else { + setWindowTitle(tr("Sonic Visualiser: %1 [%2]") + .arg(QFileInfo(m_sessionFile).fileName()) + .arg(source.getLocation())); + if (m_documentModified) { + m_documentModified = false; + documentModified(); // so as to restore "(modified)" window title + } + } + + if (!source.isRemote()) m_audioFile = source.getLocalFilename(); + + } else if (mode == CreateAdditionalModel) { + + CommandHistory::getInstance()->startCompoundOperation + (tr("Import \"%1\"").arg(source.getLocation()), true); + + m_document->addImportedModel(newModel); + + AddPaneCommand *command = new AddPaneCommand(this); + CommandHistory::getInstance()->addCommand(command); + + Pane *pane = command->getPane(); + + if (!m_timeRulerLayer) { + m_timeRulerLayer = m_document->createMainModelLayer + (LayerFactory::TimeRuler); + } + + m_document->addLayerToView(pane, m_timeRulerLayer); + + Layer *newLayer = m_document->createImportedLayer(newModel); + + if (newLayer) { + m_document->addLayerToView(pane, newLayer); + } + + CommandHistory::getInstance()->endCompoundOperation(); + + } else if (mode == ReplaceCurrentPane) { + + // We know there is a current pane, otherwise we would have + // reset the mode to CreateAdditionalModel above; and we know + // the current pane does not contain the main model, otherwise + // we would have reset it to ReplaceMainModel. But we don't + // know whether the pane contains a waveform model at all. + + Pane *pane = m_paneStack->getCurrentPane(); + Layer *replace = 0; + + for (int i = 0; i < pane->getLayerCount(); ++i) { + Layer *layer = pane->getLayer(i); + if (dynamic_cast(layer)) { + replace = layer; + break; + } + } + + CommandHistory::getInstance()->startCompoundOperation + (tr("Import \"%1\"").arg(source.getLocation()), true); + + m_document->addImportedModel(newModel); + + if (replace) { + m_document->removeLayerFromView(pane, replace); + } + + Layer *newLayer = m_document->createImportedLayer(newModel); + + if (newLayer) { + m_document->addLayerToView(pane, newLayer); + } + + CommandHistory::getInstance()->endCompoundOperation(); + } + + updateMenuStates(); + m_recentFiles.addFile(source.getLocation()); + if (!source.isRemote()) { + // for file dialog + registerLastOpenedFilePath(FileFinder::AudioFile, + source.getLocalFilename()); + } + m_openingAudioFile = false; + + currentPaneChanged(m_paneStack->getCurrentPane()); + + return FileOpenSucceeded; +} + +MainWindowBase::FileOpenStatus +MainWindowBase::openPlaylist(FileSource source, AudioFileOpenMode mode) +{ + std::set extensions; + PlaylistFileReader::getSupportedExtensions(extensions); + QString extension = source.getExtension(); + if (extensions.find(extension) == extensions.end()) return FileOpenFailed; + + if (!source.isAvailable()) return FileOpenFailed; + source.waitForData(); + + PlaylistFileReader reader(source.getLocalFilename()); + if (!reader.isOK()) return FileOpenFailed; + + PlaylistFileReader::Playlist playlist = reader.load(); + + bool someSuccess = false; + + for (PlaylistFileReader::Playlist::const_iterator i = playlist.begin(); + i != playlist.end(); ++i) { + + FileOpenStatus status = openAudio(*i, mode); + + if (status == FileOpenCancelled) { + return FileOpenCancelled; + } + + if (status == FileOpenSucceeded) { + someSuccess = true; + mode = CreateAdditionalModel; + } + } + + if (someSuccess) return FileOpenSucceeded; + else return FileOpenFailed; +} + +MainWindowBase::FileOpenStatus +MainWindowBase::openLayer(FileSource source) +{ + Pane *pane = m_paneStack->getCurrentPane(); + + if (!pane) { + // shouldn't happen, as the menu action should have been disabled + std::cerr << "WARNING: MainWindowBase::openLayer: no current pane" << std::endl; + return FileOpenWrongMode; + } + + if (!getMainModel()) { + // shouldn't happen, as the menu action should have been disabled + std::cerr << "WARNING: MainWindowBase::openLayer: No main model -- hence no default sample rate available" << std::endl; + return FileOpenWrongMode; + } + + if (!source.isAvailable()) return FileOpenFailed; + source.waitForData(); + + QString path = source.getLocalFilename(); + + if (source.getExtension() == "svl" || source.getExtension() == "xml") { + + PaneCallback callback(this); + QFile file(path); + + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + std::cerr << "ERROR: MainWindowBase::openLayer(" + << source.getLocation().toStdString() + << "): Failed to open file for reading" << std::endl; + return FileOpenFailed; + } + + SVFileReader reader(m_document, callback, source.getLocation()); + reader.setCurrentPane(pane); + + QXmlInputSource inputSource(&file); + reader.parse(inputSource); + + if (!reader.isOK()) { + std::cerr << "ERROR: MainWindowBase::openLayer(" + << source.getLocation().toStdString() + << "): Failed to read XML file: " + << reader.getErrorString().toStdString() << std::endl; + return FileOpenFailed; + } + + m_recentFiles.addFile(source.getLocation()); + + if (!source.isRemote()) { + registerLastOpenedFilePath(FileFinder::LayerFile, path); // for file dialog + } + + } else { + + try { + + Model *model = DataFileReaderFactory::load + (path, getMainModel()->getSampleRate()); + + if (model) { + + std::cerr << "MainWindowBase::openLayer: Have model" << std::endl; + + Layer *newLayer = m_document->createImportedLayer(model); + + if (newLayer) { + + m_document->addLayerToView(pane, newLayer); + m_recentFiles.addFile(source.getLocation()); + + if (!source.isRemote()) { + registerLastOpenedFilePath + (FileFinder::LayerFile, + path); // for file dialog + } + + return FileOpenSucceeded; + } + } + } catch (DataFileReaderFactory::Exception e) { + if (e == DataFileReaderFactory::ImportCancelled) { + return FileOpenCancelled; + } + } + } + + source.setLeaveLocalFile(true); + return FileOpenFailed; +} + +MainWindowBase::FileOpenStatus +MainWindowBase::openImage(FileSource source) +{ + Pane *pane = m_paneStack->getCurrentPane(); + + if (!pane) { + // shouldn't happen, as the menu action should have been disabled + std::cerr << "WARNING: MainWindowBase::openImage: no current pane" << std::endl; + return FileOpenWrongMode; + } + + if (!m_document->getMainModel()) { + return FileOpenWrongMode; + } + + bool newLayer = false; + + ImageLayer *il = dynamic_cast(pane->getSelectedLayer()); + if (!il) { + for (int i = pane->getLayerCount()-1; i >= 0; --i) { + il = dynamic_cast(pane->getLayer(i)); + if (il) break; + } + } + if (!il) { + il = dynamic_cast + (m_document->createEmptyLayer(LayerFactory::Image)); + if (!il) return FileOpenFailed; + newLayer = true; + } + + // We don't put the image file in Recent Files + + std::cerr << "openImage: trying location \"" << source.getLocation().toStdString() << "\" in image layer" << std::endl; + + if (!il->addImage(m_viewManager->getGlobalCentreFrame(), source.getLocation())) { + if (newLayer) { + m_document->setModel(il, 0); // releasing its model + delete il; + } + return FileOpenFailed; + } else { + if (newLayer) { + m_document->addLayerToView(pane, il); + } + m_paneStack->setCurrentLayer(pane, il); + } + + return FileOpenSucceeded; +} + +MainWindowBase::FileOpenStatus +MainWindowBase::openSessionFile(QString fileOrUrl) +{ + return openSession(FileSource(fileOrUrl)); +} + +MainWindowBase::FileOpenStatus +MainWindowBase::openSession(FileSource source) +{ + if (!source.isAvailable()) return FileOpenFailed; + if (source.getExtension() != "sv") return FileOpenFailed; + source.waitForData(); + + BZipFileDevice bzFile(source.getLocalFilename()); + if (!bzFile.open(QIODevice::ReadOnly)) return FileOpenFailed; + + if (!checkSaveModified()) return FileOpenCancelled; + + QString error; + closeSession(); + createDocument(); + + PaneCallback callback(this); + m_viewManager->clearSelections(); + + SVFileReader reader(m_document, callback, source.getLocation()); + QXmlInputSource inputSource(&bzFile); + reader.parse(inputSource); + + if (!reader.isOK()) { + error = tr("SV XML file read error:\n%1").arg(reader.getErrorString()); + } + + bzFile.close(); + + bool ok = (error == ""); + + if (ok) { + + setWindowTitle(tr("Sonic Visualiser: %1") + .arg(source.getLocation())); + + if (!source.isRemote()) m_sessionFile = source.getLocalFilename(); + + setupMenus(); + + CommandHistory::getInstance()->clear(); + CommandHistory::getInstance()->documentSaved(); + m_documentModified = false; + updateMenuStates(); + + m_recentFiles.addFile(source.getLocation()); + + if (!source.isRemote()) { + // for file dialog + registerLastOpenedFilePath(FileFinder::SessionFile, + source.getLocalFilename()); + } + + } else { + setWindowTitle(tr("Sonic Visualiser")); + } + + return ok ? FileOpenSucceeded : FileOpenFailed; +} + +void +MainWindowBase::createPlayTarget() +{ + if (m_playTarget) return; + + m_playTarget = AudioTargetFactory::createCallbackTarget(m_playSource); + if (!m_playTarget) { + QMessageBox::warning + (this, tr("Couldn't open audio device"), + tr("No audio available

Could not open an audio device for playback.

Audio playback will not be available during this session."), + QMessageBox::Ok); + } +} + +WaveFileModel * +MainWindowBase::getMainModel() +{ + if (!m_document) return 0; + return m_document->getMainModel(); +} + +const WaveFileModel * +MainWindowBase::getMainModel() const +{ + if (!m_document) return 0; + return m_document->getMainModel(); +} + +void +MainWindowBase::createDocument() +{ + m_document = new Document; + + connect(m_document, SIGNAL(layerAdded(Layer *)), + this, SLOT(layerAdded(Layer *))); + connect(m_document, SIGNAL(layerRemoved(Layer *)), + this, SLOT(layerRemoved(Layer *))); + connect(m_document, SIGNAL(layerAboutToBeDeleted(Layer *)), + this, SLOT(layerAboutToBeDeleted(Layer *))); + connect(m_document, SIGNAL(layerInAView(Layer *, bool)), + this, SLOT(layerInAView(Layer *, bool))); + + connect(m_document, SIGNAL(modelAdded(Model *)), + this, SLOT(modelAdded(Model *))); + connect(m_document, SIGNAL(mainModelChanged(WaveFileModel *)), + this, SLOT(mainModelChanged(WaveFileModel *))); + connect(m_document, SIGNAL(modelAboutToBeDeleted(Model *)), + this, SLOT(modelAboutToBeDeleted(Model *))); + + connect(m_document, SIGNAL(modelGenerationFailed(QString)), + this, SLOT(modelGenerationFailed(QString))); + connect(m_document, SIGNAL(modelRegenerationFailed(QString, QString)), + this, SLOT(modelRegenerationFailed(QString, QString))); +} + +bool +MainWindowBase::saveSessionFile(QString path) +{ + BZipFileDevice bzFile(path); + if (!bzFile.open(QIODevice::WriteOnly)) { + std::cerr << "Failed to open session file \"" << path.toStdString() + << "\" for writing: " + << bzFile.errorString().toStdString() << std::endl; + return false; + } + + QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); + + QTextStream out(&bzFile); + toXml(out); + out.flush(); + + QApplication::restoreOverrideCursor(); + + if (!bzFile.isOK()) { + QMessageBox::critical(this, tr("Failed to write file"), + tr("Save failed

Failed to write to file \"%1\": %2") + .arg(path).arg(bzFile.errorString())); + bzFile.close(); + return false; + } + + bzFile.close(); + return true; +} + +void +MainWindowBase::toXml(QTextStream &out) +{ + QString indent(" "); + + out << "\n"; + out << "\n"; + out << "\n"; + + m_document->toXml(out, "", ""); + + out << "\n"; + + out << QString(" \n") + .arg(width()).arg(height()); + + for (int i = 0; i < m_paneStack->getPaneCount(); ++i) { + + Pane *pane = m_paneStack->getPane(i); + + if (pane) { + pane->toXml(out, indent); + } + } + + out << "\n"; + + m_viewManager->getSelection().toXml(out); + + out << "\n"; +} + +Pane * +MainWindowBase::addPaneToStack() +{ + AddPaneCommand *command = new AddPaneCommand(this); + CommandHistory::getInstance()->addCommand(command); + return command->getPane(); +} + +void +MainWindowBase::zoomIn() +{ + Pane *currentPane = m_paneStack->getCurrentPane(); + if (currentPane) currentPane->zoom(true); +} + +void +MainWindowBase::zoomOut() +{ + Pane *currentPane = m_paneStack->getCurrentPane(); + if (currentPane) currentPane->zoom(false); +} + +void +MainWindowBase::zoomToFit() +{ + Pane *currentPane = m_paneStack->getCurrentPane(); + if (!currentPane) return; + + Model *model = getMainModel(); + if (!model) return; + + size_t start = model->getStartFrame(); + size_t end = model->getEndFrame(); + size_t pixels = currentPane->width(); + + size_t sw = currentPane->getVerticalScaleWidth(); + if (pixels > sw * 2) pixels -= sw * 2; + else pixels = 1; + if (pixels > 4) pixels -= 4; + + size_t zoomLevel = (end - start) / pixels; + + currentPane->setZoomLevel(zoomLevel); + currentPane->setCentreFrame((start + end) / 2); +} + +void +MainWindowBase::zoomDefault() +{ + Pane *currentPane = m_paneStack->getCurrentPane(); + if (currentPane) currentPane->setZoomLevel(1024); +} + +void +MainWindowBase::scrollLeft() +{ + Pane *currentPane = m_paneStack->getCurrentPane(); + if (currentPane) currentPane->scroll(false, false); +} + +void +MainWindowBase::jumpLeft() +{ + Pane *currentPane = m_paneStack->getCurrentPane(); + if (currentPane) currentPane->scroll(false, true); +} + +void +MainWindowBase::scrollRight() +{ + Pane *currentPane = m_paneStack->getCurrentPane(); + if (currentPane) currentPane->scroll(true, false); +} + +void +MainWindowBase::jumpRight() +{ + Pane *currentPane = m_paneStack->getCurrentPane(); + if (currentPane) currentPane->scroll(true, true); +} + +void +MainWindowBase::showNoOverlays() +{ + m_viewManager->setOverlayMode(ViewManager::NoOverlays); +} + +void +MainWindowBase::showMinimalOverlays() +{ + m_viewManager->setOverlayMode(ViewManager::MinimalOverlays); +} + +void +MainWindowBase::showStandardOverlays() +{ + m_viewManager->setOverlayMode(ViewManager::StandardOverlays); +} + +void +MainWindowBase::showAllOverlays() +{ + m_viewManager->setOverlayMode(ViewManager::AllOverlays); +} + +void +MainWindowBase::toggleZoomWheels() +{ + if (m_viewManager->getZoomWheelsEnabled()) { + m_viewManager->setZoomWheelsEnabled(false); + } else { + m_viewManager->setZoomWheelsEnabled(true); + } +} + +void +MainWindowBase::togglePropertyBoxes() +{ + if (m_paneStack->getLayoutStyle() == PaneStack::NoPropertyStacks) { + if (Preferences::getInstance()->getPropertyBoxLayout() == + Preferences::VerticallyStacked) { + m_paneStack->setLayoutStyle(PaneStack::PropertyStackPerPaneLayout); + } else { + m_paneStack->setLayoutStyle(PaneStack::SinglePropertyStackLayout); + } + } else { + m_paneStack->setLayoutStyle(PaneStack::NoPropertyStacks); + } +} + +void +MainWindowBase::toggleStatusBar() +{ + QSettings settings; + settings.beginGroup("MainWindow"); + bool sb = settings.value("showstatusbar", true).toBool(); + + if (sb) { + statusBar()->hide(); + } else { + statusBar()->show(); + } + + settings.setValue("showstatusbar", !sb); + + settings.endGroup(); +} + +void +MainWindowBase::preferenceChanged(PropertyContainer::PropertyName name) +{ + if (name == "Property Box Layout") { + if (m_paneStack->getLayoutStyle() != PaneStack::NoPropertyStacks) { + if (Preferences::getInstance()->getPropertyBoxLayout() == + Preferences::VerticallyStacked) { + m_paneStack->setLayoutStyle(PaneStack::PropertyStackPerPaneLayout); + } else { + m_paneStack->setLayoutStyle(PaneStack::SinglePropertyStackLayout); + } + } + } else if (name == "Background Mode" && m_viewManager) { + Preferences::BackgroundMode mode = + Preferences::getInstance()->getBackgroundMode(); + if (mode == Preferences::BackgroundFromTheme) { + m_viewManager->setGlobalDarkBackground(m_initialDarkBackground); + } else if (mode == Preferences::DarkBackground) { + m_viewManager->setGlobalDarkBackground(true); + } else { + m_viewManager->setGlobalDarkBackground(false); + } + } +} + +void +MainWindowBase::play() +{ + if (m_playSource->isPlaying()) { + stop(); + } else { + playbackFrameChanged(m_viewManager->getPlaybackFrame()); + m_playSource->play(m_viewManager->getPlaybackFrame()); + } +} + +void +MainWindowBase::ffwd() +{ + if (!getMainModel()) return; + + int frame = m_viewManager->getPlaybackFrame(); + ++frame; + + Layer *layer = getSnapLayer(); + size_t sr = getMainModel()->getSampleRate(); + + if (!layer) { + + frame = RealTime::realTime2Frame + (RealTime::frame2RealTime(frame, sr) + RealTime(2, 0), sr); + if (frame > int(getMainModel()->getEndFrame())) { + frame = getMainModel()->getEndFrame(); + } + + } else { + + size_t resolution = 0; + if (!layer->snapToFeatureFrame(m_paneStack->getCurrentPane(), + frame, resolution, Layer::SnapRight)) { + frame = getMainModel()->getEndFrame(); + } + } + + if (frame < 0) frame = 0; + + if (m_viewManager->getPlaySelectionMode()) { + frame = m_viewManager->constrainFrameToSelection(size_t(frame)); + } + + m_viewManager->setPlaybackFrame(frame); +} + +void +MainWindowBase::ffwdEnd() +{ + if (!getMainModel()) return; + + size_t frame = getMainModel()->getEndFrame(); + + if (m_viewManager->getPlaySelectionMode()) { + frame = m_viewManager->constrainFrameToSelection(frame); + } + + m_viewManager->setPlaybackFrame(frame); +} + +void +MainWindowBase::rewind() +{ + if (!getMainModel()) return; + + int frame = m_viewManager->getPlaybackFrame(); + if (frame > 0) --frame; + + Layer *layer = getSnapLayer(); + size_t sr = getMainModel()->getSampleRate(); + + // when rewinding during playback, we want to allow a period + // following a rewind target point at which the rewind will go to + // the prior point instead of the immediately neighbouring one + if (m_playSource && m_playSource->isPlaying()) { + RealTime ct = RealTime::frame2RealTime(frame, sr); + ct = ct - RealTime::fromSeconds(0.25); + if (ct < RealTime::zeroTime) ct = RealTime::zeroTime; +// std::cerr << "rewind: frame " << frame << " -> "; + frame = RealTime::realTime2Frame(ct, sr); +// std::cerr << frame << std::endl; + } + + if (!layer) { + + frame = RealTime::realTime2Frame + (RealTime::frame2RealTime(frame, sr) - RealTime(2, 0), sr); + if (frame < int(getMainModel()->getStartFrame())) { + frame = getMainModel()->getStartFrame(); + } + + } else { + + size_t resolution = 0; + if (!layer->snapToFeatureFrame(m_paneStack->getCurrentPane(), + frame, resolution, Layer::SnapLeft)) { + frame = getMainModel()->getStartFrame(); + } + } + + if (frame < 0) frame = 0; + + if (m_viewManager->getPlaySelectionMode()) { + frame = m_viewManager->constrainFrameToSelection(size_t(frame)); + } + + m_viewManager->setPlaybackFrame(frame); +} + +void +MainWindowBase::rewindStart() +{ + if (!getMainModel()) return; + + size_t frame = getMainModel()->getStartFrame(); + + if (m_viewManager->getPlaySelectionMode()) { + frame = m_viewManager->constrainFrameToSelection(frame); + } + + m_viewManager->setPlaybackFrame(frame); +} + +Layer * +MainWindowBase::getSnapLayer() const +{ + Pane *pane = m_paneStack->getCurrentPane(); + if (!pane) return 0; + + Layer *layer = pane->getSelectedLayer(); + + if (!dynamic_cast(layer) && + !dynamic_cast(layer) && + !dynamic_cast(layer)) { + + layer = 0; + + for (int i = pane->getLayerCount(); i > 0; --i) { + Layer *l = pane->getLayer(i-1); + if (dynamic_cast(l)) { + layer = l; + break; + } + } + } + + return layer; +} + +void +MainWindowBase::stop() +{ + m_playSource->stop(); + + if (m_paneStack && m_paneStack->getCurrentPane()) { + updateVisibleRangeDisplay(m_paneStack->getCurrentPane()); + } else { + m_myStatusMessage = ""; + statusBar()->showMessage(""); + } +} + +MainWindowBase::AddPaneCommand::AddPaneCommand(MainWindowBase *mw) : + m_mw(mw), + m_pane(0), + m_prevCurrentPane(0), + m_added(false) +{ +} + +MainWindowBase::AddPaneCommand::~AddPaneCommand() +{ + if (m_pane && !m_added) { + m_mw->m_paneStack->deletePane(m_pane); + } +} + +QString +MainWindowBase::AddPaneCommand::getName() const +{ + return tr("Add Pane"); +} + +void +MainWindowBase::AddPaneCommand::execute() +{ + if (!m_pane) { + m_prevCurrentPane = m_mw->m_paneStack->getCurrentPane(); + m_pane = m_mw->m_paneStack->addPane(); + + connect(m_pane, SIGNAL(contextHelpChanged(const QString &)), + m_mw, SLOT(contextHelpChanged(const QString &))); + } else { + m_mw->m_paneStack->showPane(m_pane); + } + + m_mw->m_paneStack->setCurrentPane(m_pane); + m_added = true; +} + +void +MainWindowBase::AddPaneCommand::unexecute() +{ + m_mw->m_paneStack->hidePane(m_pane); + m_mw->m_paneStack->setCurrentPane(m_prevCurrentPane); + m_added = false; +} + +MainWindowBase::RemovePaneCommand::RemovePaneCommand(MainWindowBase *mw, Pane *pane) : + m_mw(mw), + m_pane(pane), + m_added(true) +{ +} + +MainWindowBase::RemovePaneCommand::~RemovePaneCommand() +{ + if (m_pane && !m_added) { + m_mw->m_paneStack->deletePane(m_pane); + } +} + +QString +MainWindowBase::RemovePaneCommand::getName() const +{ + return tr("Remove Pane"); +} + +void +MainWindowBase::RemovePaneCommand::execute() +{ + m_prevCurrentPane = m_mw->m_paneStack->getCurrentPane(); + m_mw->m_paneStack->hidePane(m_pane); + m_added = false; +} + +void +MainWindowBase::RemovePaneCommand::unexecute() +{ + m_mw->m_paneStack->showPane(m_pane); + m_mw->m_paneStack->setCurrentPane(m_prevCurrentPane); + m_added = true; +} + +void +MainWindowBase::deleteCurrentPane() +{ + CommandHistory::getInstance()->startCompoundOperation + (tr("Delete Pane"), true); + + Pane *pane = m_paneStack->getCurrentPane(); + if (pane) { + while (pane->getLayerCount() > 0) { + Layer *layer = pane->getLayer(0); + if (layer) { + m_document->removeLayerFromView(pane, layer); + } else { + break; + } + } + + RemovePaneCommand *command = new RemovePaneCommand(this, pane); + CommandHistory::getInstance()->addCommand(command); + } + + CommandHistory::getInstance()->endCompoundOperation(); + + updateMenuStates(); +} + +void +MainWindowBase::deleteCurrentLayer() +{ + Pane *pane = m_paneStack->getCurrentPane(); + if (pane) { + Layer *layer = pane->getSelectedLayer(); + if (layer) { + m_document->removeLayerFromView(pane, layer); + } + } + updateMenuStates(); +} + +void +MainWindowBase::playbackFrameChanged(unsigned long frame) +{ + if (!(m_playSource && m_playSource->isPlaying()) || !getMainModel()) return; + + RealTime now = RealTime::frame2RealTime + (frame, getMainModel()->getSampleRate()); + + if (now.sec == m_lastPlayStatusSec) return; + + RealTime then = RealTime::frame2RealTime + (m_playSource->getPlayEndFrame(), getMainModel()->getSampleRate()); + + QString nowStr; + QString thenStr; + QString remainingStr; + + if (then.sec > 10) { + nowStr = now.toSecText().c_str(); + thenStr = then.toSecText().c_str(); + remainingStr = (then - now).toSecText().c_str(); + m_lastPlayStatusSec = now.sec; + } else { + nowStr = now.toText(true).c_str(); + thenStr = then.toText(true).c_str(); + remainingStr = (then - now).toText(true).c_str(); + } + + m_myStatusMessage = tr("Playing: %1 of %2 (%3 remaining)") + .arg(nowStr).arg(thenStr).arg(remainingStr); + + statusBar()->showMessage(m_myStatusMessage); +} + +void +MainWindowBase::globalCentreFrameChanged(unsigned long ) +{ + if ((m_playSource && m_playSource->isPlaying()) || !getMainModel()) return; + Pane *p = 0; + if (!m_paneStack || !(p = m_paneStack->getCurrentPane())) return; + if (!p->getFollowGlobalPan()) return; + updateVisibleRangeDisplay(p); +} + +void +MainWindowBase::viewCentreFrameChanged(View *v, unsigned long ) +{ + if ((m_playSource && m_playSource->isPlaying()) || !getMainModel()) return; + Pane *p = 0; + if (!m_paneStack || !(p = m_paneStack->getCurrentPane())) return; + if (v == p) updateVisibleRangeDisplay(p); +} + +void +MainWindowBase::viewZoomLevelChanged(View *v, unsigned long , bool ) +{ + if ((m_playSource && m_playSource->isPlaying()) || !getMainModel()) return; + Pane *p = 0; + if (!m_paneStack || !(p = m_paneStack->getCurrentPane())) return; + if (v == p) updateVisibleRangeDisplay(p); +} + +void +MainWindowBase::layerAdded(Layer *) +{ +// std::cerr << "MainWindowBase::layerAdded(" << layer << ")" << std::endl; + updateMenuStates(); +} + +void +MainWindowBase::layerRemoved(Layer *) +{ +// std::cerr << "MainWindowBase::layerRemoved(" << layer << ")" << std::endl; + updateMenuStates(); +} + +void +MainWindowBase::layerAboutToBeDeleted(Layer *layer) +{ +// std::cerr << "MainWindowBase::layerAboutToBeDeleted(" << layer << ")" << std::endl; + if (layer == m_timeRulerLayer) { +// std::cerr << "(this is the time ruler layer)" << std::endl; + m_timeRulerLayer = 0; + } +} + +void +MainWindowBase::layerInAView(Layer *layer, bool inAView) +{ +// std::cerr << "MainWindowBase::layerInAView(" << layer << "," << inAView << ")" << std::endl; + + // Check whether we need to add or remove model from play source + Model *model = layer->getModel(); + if (model) { + if (inAView) { + m_playSource->addModel(model); + } else { + bool found = false; + for (int i = 0; i < m_paneStack->getPaneCount(); ++i) { + Pane *pane = m_paneStack->getPane(i); + if (!pane) continue; + for (int j = 0; j < pane->getLayerCount(); ++j) { + Layer *pl = pane->getLayer(j); + if (pl && pl->getModel() == model) { + found = true; + break; + } + } + if (found) break; + } + if (!found) m_playSource->removeModel(model); + } + } + + updateMenuStates(); +} + +void +MainWindowBase::modelAdded(Model *model) +{ +// std::cerr << "MainWindowBase::modelAdded(" << model << ")" << std::endl; + m_playSource->addModel(model); +} + +void +MainWindowBase::mainModelChanged(WaveFileModel *model) +{ +// std::cerr << "MainWindowBase::mainModelChanged(" << model << ")" << std::endl; + updateDescriptionLabel(); + if (model) m_viewManager->setMainModelSampleRate(model->getSampleRate()); + if (model && !m_playTarget && m_audioOutput) createPlayTarget(); +} + +void +MainWindowBase::modelAboutToBeDeleted(Model *model) +{ +// std::cerr << "MainWindowBase::modelAboutToBeDeleted(" << model << ")" << std::endl; + if (model == m_viewManager->getPlaybackModel()) { + m_viewManager->setPlaybackModel(0); + } + m_playSource->removeModel(model); + FFTDataServer::modelAboutToBeDeleted(model); +} + +void +MainWindowBase::pollOSC() +{ + if (!m_oscQueue || m_oscQueue->isEmpty()) return; + std::cerr << "MainWindowBase::pollOSC: have " << m_oscQueue->getMessagesAvailable() << " messages" << std::endl; + + if (m_openingAudioFile) return; + + OSCMessage message = m_oscQueue->readMessage(); + + if (message.getTarget() != 0) { + return; //!!! for now -- this class is target 0, others not handled yet + } + + handleOSCMessage(message); +} + +void +MainWindowBase::inProgressSelectionChanged() +{ + Pane *currentPane = 0; + if (m_paneStack) currentPane = m_paneStack->getCurrentPane(); + if (currentPane) updateVisibleRangeDisplay(currentPane); +} + +void +MainWindowBase::contextHelpChanged(const QString &s) +{ + if (s == "" && m_myStatusMessage != "") { + statusBar()->showMessage(m_myStatusMessage); + return; + } + statusBar()->showMessage(s); +} + +void +MainWindowBase::openHelpUrl(QString url) +{ + // This method mostly lifted from Qt Assistant source code + + QProcess *process = new QProcess(this); + connect(process, SIGNAL(finished(int)), process, SLOT(deleteLater())); + + QStringList args; + +#ifdef Q_OS_MAC + args.append(url); + process->start("open", args); +#else +#ifdef Q_OS_WIN32 + + QString pf(getenv("ProgramFiles")); + QString command = pf + QString("\\Internet Explorer\\IEXPLORE.EXE"); + + args.append(url); + process->start(command, args); + +#else +#ifdef Q_WS_X11 + if (!qgetenv("KDE_FULL_SESSION").isEmpty()) { + args.append("exec"); + args.append(url); + process->start("kfmclient", args); + } else if (!qgetenv("BROWSER").isEmpty()) { + args.append(url); + process->start(qgetenv("BROWSER"), args); + } else { + args.append(url); + process->start("firefox", args); + } +#endif +#endif +#endif +} + diff -r 9ebe12983f3e -r 9ea770d93fae framework/MainWindowBase.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/framework/MainWindowBase.h Wed Oct 24 16:37:58 2007 +0000 @@ -0,0 +1,339 @@ +/* -*- 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 2006-2007 Chris Cannam and 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 _MAIN_WINDOW_BASE_H_ +#define _MAIN_WINDOW_BASE_H_ + +#include +#include +#include +#include +#include + +#include "base/Command.h" +#include "view/ViewManager.h" +#include "base/PropertyContainer.h" +#include "base/RecentFiles.h" +#include "layer/LayerFactory.h" +#include "plugin/transform/Transform.h" +#include "document/SVFileReader.h" +#include "data/fileio/FileFinder.h" +#include "data/fileio/FileSource.h" +#include + +class Document; +class PaneStack; +class Pane; +class View; +class Fader; +class Overview; +class Layer; +class WaveformLayer; +class WaveFileModel; +class AudioCallbackPlaySource; +class AudioCallbackPlayTarget; +class CommandHistory; +class QMenu; +class AudioDial; +class QLabel; +class QCheckBox; +class PreferencesDialog; +class QTreeView; +class QPushButton; +class OSCQueue; +class OSCMessage; +class KeyReference; +class Labeller; + +/** + * The base class for the SV main window. This includes everything to + * do with general document and pane stack management, but nothing + * that involves user interaction -- this doesn't create the widget or + * menu structures or editing tools, and if a function needs to open a + * dialog, it shouldn't be in here. This permits "variations on SV" + * to use different subclasses retaining the same general structure. + */ + +class MainWindowBase : public QMainWindow +{ + Q_OBJECT + +public: + MainWindowBase(bool withAudioOutput, bool withOSCSupport); + virtual ~MainWindowBase(); + + enum AudioFileOpenMode { + ReplaceMainModel, + CreateAdditionalModel, + ReplaceCurrentPane, + AskUser + }; + + enum FileOpenStatus { + FileOpenSucceeded, + FileOpenFailed, + FileOpenCancelled, + FileOpenWrongMode // attempted to open layer when no main model present + }; + + virtual FileOpenStatus open(QString fileOrUrl, AudioFileOpenMode = AskUser); + virtual FileOpenStatus open(FileSource source, AudioFileOpenMode = AskUser); + + virtual FileOpenStatus openAudio(FileSource source, AudioFileOpenMode = AskUser); + virtual FileOpenStatus openPlaylist(FileSource source, AudioFileOpenMode = AskUser); + virtual FileOpenStatus openLayer(FileSource source); + virtual FileOpenStatus openImage(FileSource source); + + virtual FileOpenStatus openSessionFile(QString fileOrUrl); + virtual FileOpenStatus openSession(FileSource source); + + virtual bool saveSessionFile(QString path); + +signals: + // Used to toggle the availability of menu actions + void canAddPane(bool); + void canDeleteCurrentPane(bool); + void canAddLayer(bool); + void canImportMoreAudio(bool); + void canImportLayer(bool); + void canExportAudio(bool); + void canExportLayer(bool); + void canExportImage(bool); + void canRenameLayer(bool); + void canEditLayer(bool); + void canMeasureLayer(bool); + void canSelect(bool); + void canClearSelection(bool); + void canEditSelection(bool); + void canDeleteSelection(bool); + void canPaste(bool); + void canInsertInstant(bool); + void canInsertInstantsAtBoundaries(bool); + void canRenumberInstants(bool); + void canDeleteCurrentLayer(bool); + void canZoom(bool); + void canScroll(bool); + void canPlay(bool); + void canFfwd(bool); + void canRewind(bool); + void canPlaySelection(bool); + void canSpeedUpPlayback(bool); + void canSlowDownPlayback(bool); + void canChangePlaybackSpeed(bool); + void canSave(bool); + +public slots: + virtual void preferenceChanged(PropertyContainer::PropertyName); + +protected slots: + virtual void zoomIn(); + virtual void zoomOut(); + virtual void zoomToFit(); + virtual void zoomDefault(); + virtual void scrollLeft(); + virtual void scrollRight(); + virtual void jumpLeft(); + virtual void jumpRight(); + + virtual void showNoOverlays(); + virtual void showMinimalOverlays(); + virtual void showStandardOverlays(); + virtual void showAllOverlays(); + + virtual void toggleZoomWheels(); + virtual void togglePropertyBoxes(); + virtual void toggleStatusBar(); + + virtual void play(); + virtual void ffwd(); + virtual void ffwdEnd(); + virtual void rewind(); + virtual void rewindStart(); + virtual void stop(); + + virtual void deleteCurrentPane(); + virtual void deleteCurrentLayer(); + + virtual void playLoopToggled(); + virtual void playSelectionToggled(); + virtual void playSoloToggled(); + + virtual void sampleRateMismatch(size_t, size_t, bool) = 0; + virtual void audioOverloadPluginDisabled() = 0; + + virtual void playbackFrameChanged(unsigned long); + virtual void globalCentreFrameChanged(unsigned long); + virtual void viewCentreFrameChanged(View *, unsigned long); + virtual void viewZoomLevelChanged(View *, unsigned long, bool); + virtual void outputLevelsChanged(float, float) = 0; + + virtual void currentPaneChanged(Pane *); + virtual void currentLayerChanged(Pane *, Layer *); + + virtual void selectAll(); + virtual void selectToStart(); + virtual void selectToEnd(); + virtual void selectVisible(); + virtual void clearSelection(); + + virtual void cut(); + virtual void copy(); + virtual void paste(); + virtual void deleteSelected(); + + virtual void insertInstant(); + virtual void insertInstantAt(size_t); + virtual void insertInstantsAtBoundaries(); + virtual void renumberInstants(); + + virtual void documentModified(); + virtual void documentRestored(); + + virtual void layerAdded(Layer *); + virtual void layerRemoved(Layer *); + virtual void layerAboutToBeDeleted(Layer *); + virtual void layerInAView(Layer *, bool); + + virtual void mainModelChanged(WaveFileModel *); + virtual void modelAdded(Model *); + virtual void modelAboutToBeDeleted(Model *); + + virtual void updateMenuStates(); + virtual void updateDescriptionLabel() = 0; + + virtual void modelGenerationFailed(QString) = 0; + virtual void modelRegenerationFailed(QString, QString) = 0; + + virtual void rightButtonMenuRequested(Pane *, QPoint point) = 0; + + virtual void paneAdded(Pane *) = 0; + virtual void paneHidden(Pane *) = 0; + virtual void paneAboutToBeDeleted(Pane *) = 0; + virtual void paneDropAccepted(Pane *, QStringList) = 0; + virtual void paneDropAccepted(Pane *, QString) = 0; + + virtual void pollOSC(); + virtual void handleOSCMessage(const OSCMessage &) = 0; + + virtual void contextHelpChanged(const QString &); + virtual void inProgressSelectionChanged(); + + virtual void closeSession() = 0; + +protected: + QString m_sessionFile; + QString m_audioFile; + Document *m_document; + + QLabel *m_descriptionLabel; + PaneStack *m_paneStack; + ViewManager *m_viewManager; + Layer *m_timeRulerLayer; + + bool m_audioOutput; + AudioCallbackPlaySource *m_playSource; + AudioCallbackPlayTarget *m_playTarget; + + OSCQueue *m_oscQueue; + + RecentFiles m_recentFiles; + RecentFiles m_recentTransforms; + + bool m_documentModified; + bool m_openingAudioFile; + bool m_abandoning; + + Labeller *m_labeller; + + int m_lastPlayStatusSec; + mutable QString m_myStatusMessage; + + bool m_initialDarkBackground; + + WaveFileModel *getMainModel(); + const WaveFileModel *getMainModel() const; + void createDocument(); + + Pane *addPaneToStack(); + Layer *getSnapLayer() const; + + class PaneCallback : public SVFileReaderPaneCallback + { + public: + PaneCallback(MainWindowBase *mw) : m_mw(mw) { } + virtual Pane *addPane() { return m_mw->addPaneToStack(); } + virtual void setWindowSize(int width, int height) { + m_mw->resize(width, height); + } + virtual void addSelection(int start, int end) { + m_mw->m_viewManager->addSelection(Selection(start, end)); + } + protected: + MainWindowBase *m_mw; + }; + + class AddPaneCommand : public Command + { + public: + AddPaneCommand(MainWindowBase *mw); + virtual ~AddPaneCommand(); + + virtual void execute(); + virtual void unexecute(); + virtual QString getName() const; + + Pane *getPane() { return m_pane; } + + protected: + MainWindowBase *m_mw; + Pane *m_pane; // Main window owns this, but I determine its lifespan + Pane *m_prevCurrentPane; // I don't own this + bool m_added; + }; + + class RemovePaneCommand : public Command + { + public: + RemovePaneCommand(MainWindowBase *mw, Pane *pane); + virtual ~RemovePaneCommand(); + + virtual void execute(); + virtual void unexecute(); + virtual QString getName() const; + + protected: + MainWindowBase *m_mw; + Pane *m_pane; // Main window owns this, but I determine its lifespan + Pane *m_prevCurrentPane; // I don't own this + bool m_added; + }; + + virtual bool checkSaveModified() = 0; + + virtual QString getOpenFileName(FileFinder::FileType type); + virtual QString getSaveFileName(FileFinder::FileType type); + virtual void registerLastOpenedFilePath(FileFinder::FileType type, QString path); + + virtual void createPlayTarget(); + virtual void openHelpUrl(QString url); + + virtual void setupMenus() = 0; + virtual void updateVisibleRangeDisplay(Pane *p) const = 0; + + virtual void toXml(QTextStream &stream); +}; + + +#endif diff -r 9ebe12983f3e -r 9ea770d93fae framework/SVFileReader.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/framework/SVFileReader.cpp Wed Oct 24 16:37:58 2007 +0000 @@ -0,0 +1,1167 @@ +/* -*- 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 2006 Chris Cannam and 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 "SVFileReader.h" + +#include "layer/Layer.h" +#include "view/View.h" +#include "base/PlayParameters.h" +#include "base/PlayParameterRepository.h" + +#include "data/fileio/AudioFileReaderFactory.h" +#include "data/fileio/FileFinder.h" +#include "data/fileio/FileSource.h" + +#include "data/model/WaveFileModel.h" +#include "data/model/EditableDenseThreeDimensionalModel.h" +#include "data/model/SparseOneDimensionalModel.h" +#include "data/model/SparseTimeValueModel.h" +#include "data/model/NoteModel.h" +#include "data/model/TextModel.h" +#include "data/model/ImageModel.h" + +#include "view/Pane.h" + +#include "Document.h" + +#include +#include +#include + +#include + +SVFileReader::SVFileReader(Document *document, + SVFileReaderPaneCallback &callback, + QString location) : + m_document(document), + m_paneCallback(callback), + m_location(location), + m_currentPane(0), + m_currentDataset(0), + m_currentDerivedModel(0), + m_currentDerivedModelId(-1), + m_currentPlayParameters(0), + m_datasetSeparator(" "), + m_inRow(false), + m_inLayer(false), + m_inView(false), + m_rowNumber(0), + m_ok(false) +{ +} + +void +SVFileReader::parse(const QString &xmlData) +{ + QXmlInputSource inputSource; + inputSource.setData(xmlData); + parse(inputSource); +} + +void +SVFileReader::parse(QXmlInputSource &inputSource) +{ + QXmlSimpleReader reader; + reader.setContentHandler(this); + reader.setErrorHandler(this); + m_ok = reader.parse(inputSource); +} + +bool +SVFileReader::isOK() +{ + return m_ok; +} + +SVFileReader::~SVFileReader() +{ + if (!m_awaitingDatasets.empty()) { + std::cerr << "WARNING: SV-XML: File ended with " + << m_awaitingDatasets.size() << " unfilled model dataset(s)" + << std::endl; + } + + std::set unaddedModels; + + for (std::map::iterator i = m_models.begin(); + i != m_models.end(); ++i) { + if (m_addedModels.find(i->second) == m_addedModels.end()) { + unaddedModels.insert(i->second); + } + } + + if (!unaddedModels.empty()) { + std::cerr << "WARNING: SV-XML: File contained " + << unaddedModels.size() << " unused models" + << std::endl; + while (!unaddedModels.empty()) { + delete *unaddedModels.begin(); + unaddedModels.erase(unaddedModels.begin()); + } + } +} + +bool +SVFileReader::startElement(const QString &, const QString &, + const QString &qName, + const QXmlAttributes &attributes) +{ + QString name = qName.toLower(); + + bool ok = false; + + // Valid element names: + // + // sv + // data + // dataset + // display + // derivation + // playparameters + // layer + // model + // point + // row + // view + // window + + if (name == "sv") { + + // nothing needed + ok = true; + + } else if (name == "data") { + + // nothing needed + m_inData = true; + ok = true; + + } else if (name == "display") { + + // nothing needed + ok = true; + + } else if (name == "window") { + + ok = readWindow(attributes); + + } else if (name == "model") { + + ok = readModel(attributes); + + } else if (name == "dataset") { + + ok = readDatasetStart(attributes); + + } else if (name == "bin") { + + ok = addBinToDataset(attributes); + + } else if (name == "point") { + + ok = addPointToDataset(attributes); + + } else if (name == "row") { + + ok = addRowToDataset(attributes); + + } else if (name == "layer") { + + addUnaddedModels(); // all models must be specified before first layer + ok = readLayer(attributes); + + } else if (name == "view") { + + m_inView = true; + ok = readView(attributes); + + } else if (name == "derivation") { + + ok = readDerivation(attributes); + + } else if (name == "playparameters") { + + ok = readPlayParameters(attributes); + + } else if (name == "plugin") { + + ok = readPlugin(attributes); + + } else if (name == "selections") { + + m_inSelections = true; + ok = true; + + } else if (name == "selection") { + + ok = readSelection(attributes); + + } else if (name == "measurement") { + + ok = readMeasurement(attributes); + + } else { + std::cerr << "WARNING: SV-XML: Unexpected element \"" + << name.toLocal8Bit().data() << "\"" << std::endl; + } + + if (!ok) { + std::cerr << "WARNING: SV-XML: Failed to completely process element \"" + << name.toLocal8Bit().data() << "\"" << std::endl; + } + + return true; +} + +bool +SVFileReader::characters(const QString &text) +{ + bool ok = false; + + if (m_inRow) { + ok = readRowData(text); + if (!ok) { + std::cerr << "WARNING: SV-XML: Failed to read row data content for row " << m_rowNumber << std::endl; + } + } + + return true; +} + +bool +SVFileReader::endElement(const QString &, const QString &, + const QString &qName) +{ + QString name = qName.toLower(); + + if (name == "dataset") { + + if (m_currentDataset) { + + bool foundInAwaiting = false; + + for (std::map::iterator i = m_awaitingDatasets.begin(); + i != m_awaitingDatasets.end(); ++i) { + if (haveModel(i->second) && + m_models[i->second] == m_currentDataset) { + m_awaitingDatasets.erase(i); + foundInAwaiting = true; + break; + } + } + + if (!foundInAwaiting) { + std::cerr << "WARNING: SV-XML: Dataset precedes model, or no model uses dataset" << std::endl; + } + } + + m_currentDataset = 0; + + } else if (name == "data") { + + addUnaddedModels(); + m_inData = false; + + } else if (name == "derivation") { + + if (!m_currentDerivedModel) { + if (m_currentDerivedModel < 0) { + std::cerr << "WARNING: SV-XML: Bad derivation output model id " + << m_currentDerivedModelId << std::endl; + } else if (haveModel(m_currentDerivedModelId)) { + std::cerr << "WARNING: SV-XML: Derivation has existing model " + << m_currentDerivedModelId + << " as target, not regenerating" << std::endl; + } else { + m_currentDerivedModel = m_models[m_currentDerivedModelId] = + m_document->addDerivedModel(m_currentTransform, + m_currentTransformSource, + m_currentTransformContext, + m_currentTransformConfiguration); + } + } else { + m_document->addDerivedModel(m_currentTransform, + m_currentTransformSource, + m_currentTransformContext, + m_currentDerivedModel, + m_currentTransformConfiguration); + } + + m_addedModels.insert(m_currentDerivedModel); + m_currentDerivedModel = 0; + m_currentDerivedModelId = -1; + m_currentTransform = ""; + m_currentTransformConfiguration = ""; + + } else if (name == "row") { + m_inRow = false; + } else if (name == "layer") { + m_inLayer = false; + } else if (name == "view") { + m_inView = false; + } else if (name == "selections") { + m_inSelections = false; + } else if (name == "playparameters") { + m_currentPlayParameters = 0; + } + + return true; +} + +bool +SVFileReader::error(const QXmlParseException &exception) +{ + m_errorString = + QString("ERROR: SV-XML: %1 at line %2, column %3") + .arg(exception.message()) + .arg(exception.lineNumber()) + .arg(exception.columnNumber()); + std::cerr << m_errorString.toLocal8Bit().data() << std::endl; + return QXmlDefaultHandler::error(exception); +} + +bool +SVFileReader::fatalError(const QXmlParseException &exception) +{ + m_errorString = + QString("FATAL ERROR: SV-XML: %1 at line %2, column %3") + .arg(exception.message()) + .arg(exception.lineNumber()) + .arg(exception.columnNumber()); + std::cerr << m_errorString.toLocal8Bit().data() << std::endl; + return QXmlDefaultHandler::fatalError(exception); +} + + +#define READ_MANDATORY(TYPE, NAME, CONVERSION) \ + TYPE NAME = attributes.value(#NAME).trimmed().CONVERSION(&ok); \ + if (!ok) { \ + std::cerr << "WARNING: SV-XML: Missing or invalid mandatory " #TYPE " attribute \"" #NAME "\"" << std::endl; \ + return false; \ + } + +bool +SVFileReader::readWindow(const QXmlAttributes &attributes) +{ + bool ok = false; + + READ_MANDATORY(int, width, toInt); + READ_MANDATORY(int, height, toInt); + + m_paneCallback.setWindowSize(width, height); + return true; +} + +void +SVFileReader::addUnaddedModels() +{ + std::set unaddedModels; + + for (std::map::iterator i = m_models.begin(); + i != m_models.end(); ++i) { + if (m_addedModels.find(i->second) == m_addedModels.end()) { + unaddedModels.insert(i->second); + } + } + + for (std::set::iterator i = unaddedModels.begin(); + i != unaddedModels.end(); ++i) { + m_document->addImportedModel(*i); + m_addedModels.insert(*i); + } +} + +bool +SVFileReader::readModel(const QXmlAttributes &attributes) +{ + bool ok = false; + + READ_MANDATORY(int, id, toInt); + + if (haveModel(id)) { + std::cerr << "WARNING: SV-XML: Ignoring duplicate model id " << id + << std::endl; + return false; + } + + QString name = attributes.value("name"); + + std::cerr << "SVFileReader::readModel: model name \"" << name.toStdString() << "\"" << std::endl; + + READ_MANDATORY(int, sampleRate, toInt); + + QString type = attributes.value("type").trimmed(); + bool mainModel = (attributes.value("mainModel").trimmed() == "true"); + + if (type == "wavefile") { + + WaveFileModel *model = 0; + FileFinder *ff = FileFinder::getInstance(); + QString originalPath = attributes.value("file"); + QString path = ff->find(FileFinder::AudioFile, + originalPath, m_location); + + FileSource file(path); + file.waitForStatus(); + + if (!file.isOK()) { + std::cerr << "SVFileReader::readModel: Failed to retrieve file \"" << path.toStdString() << "\" for wave file model: " << file.getErrorString().toStdString() << std::endl; + } else if (!file.isAvailable()) { + std::cerr << "SVFileReader::readModel: Failed to retrieve file \"" << path.toStdString() << "\" for wave file model: Source unavailable" << std::endl; + } else { + + file.waitForData(); + model = new WaveFileModel(file); + if (!model->isOK()) { + delete model; + model = 0; + } + } + + if (!model) return false; + + model->setObjectName(name); + m_models[id] = model; + if (mainModel) { + m_document->setMainModel(model); + m_addedModels.insert(model); + } + // Derived models will be added when their derivation + // is found. + + return true; + + } else if (type == "dense") { + + READ_MANDATORY(int, dimensions, toInt); + + // Currently the only dense model we support here is the dense + // 3d model. Dense time-value models are always file-backed + // waveform data, at this point, and they come in as wavefile + // models. + + if (dimensions == 3) { + + READ_MANDATORY(int, windowSize, toInt); + READ_MANDATORY(int, yBinCount, toInt); + + EditableDenseThreeDimensionalModel *model = + new EditableDenseThreeDimensionalModel + (sampleRate, windowSize, yBinCount); + + float minimum = attributes.value("minimum").trimmed().toFloat(&ok); + if (ok) model->setMinimumLevel(minimum); + + float maximum = attributes.value("maximum").trimmed().toFloat(&ok); + if (ok) model->setMaximumLevel(maximum); + + int dataset = attributes.value("dataset").trimmed().toInt(&ok); + if (ok) m_awaitingDatasets[dataset] = id; + + model->setObjectName(name); + m_models[id] = model; + return true; + + } else { + + std::cerr << "WARNING: SV-XML: Unexpected dense model dimension (" + << dimensions << ")" << std::endl; + } + } else if (type == "sparse") { + + READ_MANDATORY(int, dimensions, toInt); + + if (dimensions == 1) { + + READ_MANDATORY(int, resolution, toInt); + + if (attributes.value("subtype") == "image") { + + bool notifyOnAdd = (attributes.value("notifyOnAdd") == "true"); + ImageModel *model = new ImageModel(sampleRate, resolution, + notifyOnAdd); + model->setObjectName(name); + m_models[id] = model; + + } else { + + SparseOneDimensionalModel *model = new SparseOneDimensionalModel + (sampleRate, resolution); + model->setObjectName(name); + m_models[id] = model; + } + + int dataset = attributes.value("dataset").trimmed().toInt(&ok); + if (ok) m_awaitingDatasets[dataset] = id; + + return true; + + } else if (dimensions == 2 || dimensions == 3) { + + READ_MANDATORY(int, resolution, toInt); + + bool haveMinMax = true; + float minimum = attributes.value("minimum").trimmed().toFloat(&ok); + if (!ok) haveMinMax = false; + float maximum = attributes.value("maximum").trimmed().toFloat(&ok); + if (!ok) haveMinMax = false; + + float valueQuantization = + attributes.value("valueQuantization").trimmed().toFloat(&ok); + + bool notifyOnAdd = (attributes.value("notifyOnAdd") == "true"); + + QString units = attributes.value("units"); + + if (dimensions == 2) { + if (attributes.value("subtype") == "text") { + TextModel *model = new TextModel + (sampleRate, resolution, notifyOnAdd); + model->setObjectName(name); + m_models[id] = model; + } else { + SparseTimeValueModel *model; + if (haveMinMax) { + model = new SparseTimeValueModel + (sampleRate, resolution, minimum, maximum, notifyOnAdd); + } else { + model = new SparseTimeValueModel + (sampleRate, resolution, notifyOnAdd); + } + model->setScaleUnits(units); + model->setObjectName(name); + m_models[id] = model; + } + } else { + NoteModel *model; + if (haveMinMax) { + model = new NoteModel + (sampleRate, resolution, minimum, maximum, notifyOnAdd); + } else { + model = new NoteModel + (sampleRate, resolution, notifyOnAdd); + } + model->setValueQuantization(valueQuantization); + model->setScaleUnits(units); + model->setObjectName(name); + m_models[id] = model; + } + + int dataset = attributes.value("dataset").trimmed().toInt(&ok); + if (ok) m_awaitingDatasets[dataset] = id; + + return true; + + } else { + + std::cerr << "WARNING: SV-XML: Unexpected sparse model dimension (" + << dimensions << ")" << std::endl; + } + } else { + + std::cerr << "WARNING: SV-XML: Unexpected model type \"" + << type.toLocal8Bit().data() << "\" for model id " << id << std::endl; + } + + return false; +} + +bool +SVFileReader::readView(const QXmlAttributes &attributes) +{ + QString type = attributes.value("type"); + m_currentPane = 0; + + if (type != "pane") { + std::cerr << "WARNING: SV-XML: Unexpected view type \"" + << type.toLocal8Bit().data() << "\"" << std::endl; + return false; + } + + m_currentPane = m_paneCallback.addPane(); + + if (!m_currentPane) { + std::cerr << "WARNING: SV-XML: Internal error: Failed to add pane!" + << std::endl; + return false; + } + + bool ok = false; + + View *view = m_currentPane; + + // The view properties first + + READ_MANDATORY(size_t, centre, toUInt); + READ_MANDATORY(size_t, zoom, toUInt); + READ_MANDATORY(int, followPan, toInt); + READ_MANDATORY(int, followZoom, toInt); + QString tracking = attributes.value("tracking"); + + // Specify the follow modes before we set the actual values + view->setFollowGlobalPan(followPan); + view->setFollowGlobalZoom(followZoom); + view->setPlaybackFollow(tracking == "scroll" ? PlaybackScrollContinuous : + tracking == "page" ? PlaybackScrollPage + : PlaybackIgnore); + + // Then set these values + view->setCentreFrame(centre); + view->setZoomLevel(zoom); + + // And pane properties + READ_MANDATORY(int, centreLineVisible, toInt); + m_currentPane->setCentreLineVisible(centreLineVisible); + + int height = attributes.value("height").toInt(&ok); + if (ok) { + m_currentPane->resize(m_currentPane->width(), height); + } + + return true; +} + +bool +SVFileReader::readLayer(const QXmlAttributes &attributes) +{ + QString type = attributes.value("type"); + + int id; + bool ok = false; + id = attributes.value("id").trimmed().toInt(&ok); + + if (!ok) { + std::cerr << "WARNING: SV-XML: No layer id for layer of type \"" + << type.toLocal8Bit().data() + << "\"" << std::endl; + return false; + } + + Layer *layer = 0; + bool isNewLayer = false; + + // Layers are expected to be defined in layer elements in the data + // section, and referred to in layer elements in the view + // sections. So if we're in the data section, we expect this + // layer not to exist already; if we're in the view section, we + // expect it to exist. + + if (m_inData) { + + if (m_layers.find(id) != m_layers.end()) { + std::cerr << "WARNING: SV-XML: Ignoring duplicate layer id " << id + << " in data section" << std::endl; + return false; + } + + layer = m_layers[id] = m_document->createLayer + (LayerFactory::getInstance()->getLayerTypeForName(type)); + + if (layer) { + m_layers[id] = layer; + isNewLayer = true; + } + + } else { + + if (!m_currentPane) { + std::cerr << "WARNING: SV-XML: No current pane for layer " << id + << " in view section" << std::endl; + return false; + } + + if (m_layers.find(id) != m_layers.end()) { + + layer = m_layers[id]; + + } else { + std::cerr << "WARNING: SV-XML: Layer id " << id + << " in view section has not been defined -- defining it here" + << std::endl; + + layer = m_document->createLayer + (LayerFactory::getInstance()->getLayerTypeForName(type)); + + if (layer) { + m_layers[id] = layer; + isNewLayer = true; + } + } + } + + if (!layer) { + std::cerr << "WARNING: SV-XML: Failed to add layer of type \"" + << type.toLocal8Bit().data() + << "\"" << std::endl; + return false; + } + + if (isNewLayer) { + + QString name = attributes.value("name"); + layer->setObjectName(name); + + int modelId; + bool modelOk = false; + modelId = attributes.value("model").trimmed().toInt(&modelOk); + + if (modelOk) { + if (haveModel(modelId)) { + Model *model = m_models[modelId]; + m_document->setModel(layer, model); + } else { + std::cerr << "WARNING: SV-XML: Unknown model id " << modelId + << " in layer definition" << std::endl; + } + } + + layer->setProperties(attributes); + } + + if (!m_inData && m_currentPane) { + + QString visible = attributes.value("visible"); + bool dormant = (visible == "false"); + + // We need to do this both before and after adding the layer + // to the view -- we need it to be dormant if appropriate + // before it's actually added to the view so that any property + // box gets the right state when it's added, but the add layer + // command sets dormant to false because it assumes it may be + // restoring a previously dormant layer, so we need to set it + // again afterwards too. Hm + layer->setLayerDormant(m_currentPane, dormant); + + m_document->addLayerToView(m_currentPane, layer); + + layer->setLayerDormant(m_currentPane, dormant); + } + + m_currentLayer = layer; + m_inLayer = true; + + return true; +} + +bool +SVFileReader::readDatasetStart(const QXmlAttributes &attributes) +{ + bool ok = false; + + READ_MANDATORY(int, id, toInt); + READ_MANDATORY(int, dimensions, toInt); + + if (m_awaitingDatasets.find(id) == m_awaitingDatasets.end()) { + std::cerr << "WARNING: SV-XML: Unwanted dataset " << id << std::endl; + return false; + } + + int modelId = m_awaitingDatasets[id]; + + Model *model = 0; + if (haveModel(modelId)) { + model = m_models[modelId]; + } else { + std::cerr << "WARNING: SV-XML: Internal error: Unknown model " << modelId + << " expecting dataset " << id << std::endl; + return false; + } + + bool good = false; + + switch (dimensions) { + case 1: + if (dynamic_cast(model)) good = true; + else if (dynamic_cast(model)) good = true; + break; + + case 2: + if (dynamic_cast(model)) good = true; + else if (dynamic_cast(model)) good = true; + break; + + case 3: + if (dynamic_cast(model)) good = true; + else if (dynamic_cast(model)) { + m_datasetSeparator = attributes.value("separator"); + good = true; + } + break; + } + + if (!good) { + std::cerr << "WARNING: SV-XML: Model id " << modelId << " has wrong number of dimensions or inappropriate type for " << dimensions << "-D dataset " << id << std::endl; + m_currentDataset = 0; + return false; + } + + m_currentDataset = model; + return true; +} + +bool +SVFileReader::addPointToDataset(const QXmlAttributes &attributes) +{ + bool ok = false; + + READ_MANDATORY(int, frame, toInt); + +// std::cerr << "SVFileReader::addPointToDataset: frame = " << frame << std::endl; + + SparseOneDimensionalModel *sodm = dynamic_cast + (m_currentDataset); + + if (sodm) { +// std::cerr << "Current dataset is a sparse one dimensional model" << std::endl; + QString label = attributes.value("label"); + sodm->addPoint(SparseOneDimensionalModel::Point(frame, label)); + return true; + } + + SparseTimeValueModel *stvm = dynamic_cast + (m_currentDataset); + + if (stvm) { +// std::cerr << "Current dataset is a sparse time-value model" << std::endl; + float value = 0.0; + value = attributes.value("value").trimmed().toFloat(&ok); + QString label = attributes.value("label"); + stvm->addPoint(SparseTimeValueModel::Point(frame, value, label)); + return ok; + } + + NoteModel *nm = dynamic_cast(m_currentDataset); + + if (nm) { +// std::cerr << "Current dataset is a note model" << std::endl; + float value = 0.0; + value = attributes.value("value").trimmed().toFloat(&ok); + size_t duration = 0; + duration = attributes.value("duration").trimmed().toUInt(&ok); + QString label = attributes.value("label"); + nm->addPoint(NoteModel::Point(frame, value, duration, label)); + return ok; + } + + TextModel *tm = dynamic_cast(m_currentDataset); + + if (tm) { +// std::cerr << "Current dataset is a text model" << std::endl; + float height = 0.0; + height = attributes.value("height").trimmed().toFloat(&ok); + QString label = attributes.value("label"); +// std::cerr << "SVFileReader::addPointToDataset: TextModel: frame = " << frame << ", height = " << height << ", label = " << label.toStdString() << ", ok = " << ok << std::endl; + tm->addPoint(TextModel::Point(frame, height, label)); + return ok; + } + + ImageModel *im = dynamic_cast(m_currentDataset); + + if (im) { +// std::cerr << "Current dataset is an image model" << std::endl; + QString image = attributes.value("image"); + QString label = attributes.value("label"); +// std::cerr << "SVFileReader::addPointToDataset: ImageModel: frame = " << frame << ", image = " << image.toStdString() << ", label = " << label.toStdString() << ", ok = " << ok << std::endl; + im->addPoint(ImageModel::Point(frame, image, label)); + return ok; + } + + std::cerr << "WARNING: SV-XML: Point element found in non-point dataset" << std::endl; + + return false; +} + +bool +SVFileReader::addBinToDataset(const QXmlAttributes &attributes) +{ + EditableDenseThreeDimensionalModel *dtdm = + dynamic_cast + (m_currentDataset); + + if (dtdm) { + + bool ok = false; + int n = attributes.value("number").trimmed().toInt(&ok); + if (!ok) { + std::cerr << "WARNING: SV-XML: Missing or invalid bin number" + << std::endl; + return false; + } + + QString name = attributes.value("name"); + + dtdm->setBinName(n, name); + return true; + } + + std::cerr << "WARNING: SV-XML: Bin definition found in incompatible dataset" << std::endl; + + return false; +} + + +bool +SVFileReader::addRowToDataset(const QXmlAttributes &attributes) +{ + m_inRow = false; + + bool ok = false; + m_rowNumber = attributes.value("n").trimmed().toInt(&ok); + if (!ok) { + std::cerr << "WARNING: SV-XML: Missing or invalid row number" + << std::endl; + return false; + } + + m_inRow = true; + +// std::cerr << "SV-XML: In row " << m_rowNumber << std::endl; + + return true; +} + +bool +SVFileReader::readRowData(const QString &text) +{ + EditableDenseThreeDimensionalModel *dtdm = + dynamic_cast + (m_currentDataset); + + bool warned = false; + + if (dtdm) { + QStringList data = text.split(m_datasetSeparator); + + DenseThreeDimensionalModel::Column values; + + for (QStringList::iterator i = data.begin(); i != data.end(); ++i) { + + if (values.size() == dtdm->getHeight()) { + if (!warned) { + std::cerr << "WARNING: SV-XML: Too many y-bins in 3-D dataset row " + << m_rowNumber << std::endl; + warned = true; + } + } + + bool ok; + float value = i->toFloat(&ok); + if (!ok) { + std::cerr << "WARNING: SV-XML: Bad floating-point value " + << i->toLocal8Bit().data() + << " in row data" << std::endl; + } else { + values.push_back(value); + } + } + + dtdm->setColumn(m_rowNumber, values); + return true; + } + + std::cerr << "WARNING: SV-XML: Row data found in non-row dataset" << std::endl; + + return false; +} + +bool +SVFileReader::readDerivation(const QXmlAttributes &attributes) +{ + int modelId = 0; + bool modelOk = false; + modelId = attributes.value("model").trimmed().toInt(&modelOk); + + if (!modelOk) { + std::cerr << "WARNING: SV-XML: No model id specified for derivation" << std::endl; + return false; + } + + QString transform = attributes.value("transform"); + + if (haveModel(modelId)) { + m_currentDerivedModel = m_models[modelId]; + } else { + // we'll regenerate the model when the derivation element ends + m_currentDerivedModel = 0; + } + + m_currentDerivedModelId = modelId; + + int sourceId = 0; + bool sourceOk = false; + sourceId = attributes.value("source").trimmed().toInt(&sourceOk); + + if (sourceOk && haveModel(sourceId)) { + m_currentTransformSource = m_models[sourceId]; + } else { + m_currentTransformSource = m_document->getMainModel(); + } + + m_currentTransform = transform; + m_currentTransformConfiguration = ""; + + m_currentTransformContext = PluginTransform::ExecutionContext(); + + bool ok = false; + int channel = attributes.value("channel").trimmed().toInt(&ok); + if (ok) m_currentTransformContext.channel = channel; + + int domain = attributes.value("domain").trimmed().toInt(&ok); + if (ok) m_currentTransformContext.domain = Vamp::Plugin::InputDomain(domain); + + int stepSize = attributes.value("stepSize").trimmed().toInt(&ok); + if (ok) m_currentTransformContext.stepSize = stepSize; + + int blockSize = attributes.value("blockSize").trimmed().toInt(&ok); + if (ok) m_currentTransformContext.blockSize = blockSize; + + int windowType = attributes.value("windowType").trimmed().toInt(&ok); + if (ok) m_currentTransformContext.windowType = WindowType(windowType); + + QString startFrameStr = attributes.value("startFrame"); + QString durationStr = attributes.value("duration"); + + size_t startFrame = 0; + size_t duration = 0; + + if (startFrameStr != "") { + startFrame = startFrameStr.trimmed().toInt(&ok); + if (!ok) startFrame = 0; + } + if (durationStr != "") { + duration = durationStr.trimmed().toInt(&ok); + if (!ok) duration = 0; + } + + m_currentTransformContext.startFrame = startFrame; + m_currentTransformContext.duration = duration; + + return true; +} + +bool +SVFileReader::readPlayParameters(const QXmlAttributes &attributes) +{ + m_currentPlayParameters = 0; + + int modelId = 0; + bool modelOk = false; + modelId = attributes.value("model").trimmed().toInt(&modelOk); + + if (!modelOk) { + std::cerr << "WARNING: SV-XML: No model id specified for play parameters" << std::endl; + return false; + } + + if (haveModel(modelId)) { + + bool ok = false; + + PlayParameters *parameters = PlayParameterRepository::getInstance()-> + getPlayParameters(m_models[modelId]); + + if (!parameters) { + std::cerr << "WARNING: SV-XML: Play parameters for model " + << modelId + << " not found - has model been added to document?" + << std::endl; + return false; + } + + bool muted = (attributes.value("mute").trimmed() == "true"); + parameters->setPlayMuted(muted); + + float pan = attributes.value("pan").toFloat(&ok); + if (ok) parameters->setPlayPan(pan); + + float gain = attributes.value("gain").toFloat(&ok); + if (ok) parameters->setPlayGain(gain); + + QString pluginId = attributes.value("pluginId"); + if (pluginId != "") parameters->setPlayPluginId(pluginId); + + m_currentPlayParameters = parameters; + +// std::cerr << "Current play parameters for model: " << m_models[modelId] << ": " << m_currentPlayParameters << std::endl; + + } else { + + std::cerr << "WARNING: SV-XML: Unknown model " << modelId + << " for play parameters" << std::endl; + return false; + } + + return true; +} + +bool +SVFileReader::readPlugin(const QXmlAttributes &attributes) +{ + if (m_currentDerivedModelId < 0 && !m_currentPlayParameters) { + std::cerr << "WARNING: SV-XML: Plugin found outside derivation or play parameters" << std::endl; + return false; + } + + QString configurationXml = "setPlayPluginConfiguration(configurationXml); + } else { + m_currentTransformConfiguration += configurationXml; + } + + return true; +} + +bool +SVFileReader::readSelection(const QXmlAttributes &attributes) +{ + bool ok; + + READ_MANDATORY(int, start, toInt); + READ_MANDATORY(int, end, toInt); + + m_paneCallback.addSelection(start, end); + + return true; +} + +bool +SVFileReader::readMeasurement(const QXmlAttributes &attributes) +{ + std::cerr << "SVFileReader::readMeasurement: inLayer " + << m_inLayer << ", layer " << m_currentLayer << std::endl; + + if (!m_inLayer) { + std::cerr << "WARNING: SV-XML: Measurement found outside layer" << std::endl; + return false; + } + + m_currentLayer->addMeasurementRect(attributes); + return true; +} + +SVFileReaderPaneCallback::~SVFileReaderPaneCallback() +{ +} + diff -r 9ebe12983f3e -r 9ea770d93fae framework/SVFileReader.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/framework/SVFileReader.h Wed Oct 24 16:37:58 2007 +0000 @@ -0,0 +1,234 @@ +/* -*- 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 2006 Chris Cannam and 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 _SV_FILE_READER_H_ +#define _SV_FILE_READER_H_ + +#include "layer/LayerFactory.h" +#include "plugin/transform/Transform.h" +#include "plugin/transform/PluginTransform.h" + +#include + +#include + +class Pane; +class Model; +class Document; +class PlayParameters; + +class SVFileReaderPaneCallback +{ +public: + virtual ~SVFileReaderPaneCallback(); + virtual Pane *addPane() = 0; + virtual void setWindowSize(int width, int height) = 0; + virtual void addSelection(int start, int end) = 0; +}; + +/** + SVFileReader loads Sonic Visualiser XML files. (The SV file + format is bzipped XML.) + + Some notes about the SV XML format follow. We're very lazy with + our XML: there's no schema or DTD, and we depend heavily on + elements being in a particular order. + +\verbatim + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +\endverbatim + */ + + +class SVFileReader : public QXmlDefaultHandler +{ +public: + SVFileReader(Document *document, + SVFileReaderPaneCallback &callback, + QString location = ""); // for audio file locate mechanism + virtual ~SVFileReader(); + + void parse(const QString &xmlData); + void parse(QXmlInputSource &source); + + bool isOK(); + QString getErrorString() const { return m_errorString; } + + // For loading a single layer onto an existing pane + void setCurrentPane(Pane *pane) { m_currentPane = pane; } + + virtual bool startElement(const QString &namespaceURI, + const QString &localName, + const QString &qName, + const QXmlAttributes& atts); + + virtual bool characters(const QString &); + + virtual bool endElement(const QString &namespaceURI, + const QString &localName, + const QString &qName); + + bool error(const QXmlParseException &exception); + bool fatalError(const QXmlParseException &exception); + +protected: + bool readWindow(const QXmlAttributes &); + bool readModel(const QXmlAttributes &); + bool readView(const QXmlAttributes &); + bool readLayer(const QXmlAttributes &); + bool readDatasetStart(const QXmlAttributes &); + bool addBinToDataset(const QXmlAttributes &); + bool addPointToDataset(const QXmlAttributes &); + bool addRowToDataset(const QXmlAttributes &); + bool readRowData(const QString &); + bool readDerivation(const QXmlAttributes &); + bool readPlayParameters(const QXmlAttributes &); + bool readPlugin(const QXmlAttributes &); + bool readSelection(const QXmlAttributes &); + bool readMeasurement(const QXmlAttributes &); + void addUnaddedModels(); + + bool haveModel(int id) { + return (m_models.find(id) != m_models.end()) && m_models[id]; + } + + Document *m_document; + SVFileReaderPaneCallback &m_paneCallback; + QString m_location; + Pane *m_currentPane; + std::map m_layers; + std::map m_models; + std::set m_addedModels; + std::map m_awaitingDatasets; // map dataset id -> model id + Layer *m_currentLayer; + Model *m_currentDataset; + Model *m_currentDerivedModel; + int m_currentDerivedModelId; + PlayParameters *m_currentPlayParameters; + QString m_currentTransform; + Model *m_currentTransformSource; + PluginTransform::ExecutionContext m_currentTransformContext; + QString m_currentTransformConfiguration; + QString m_datasetSeparator; + bool m_inRow; + bool m_inLayer; + bool m_inView; + bool m_inData; + bool m_inSelections; + int m_rowNumber; + QString m_errorString; + bool m_ok; +}; + +#endif diff -r 9ebe12983f3e -r 9ea770d93fae framework/document.pro --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/framework/document.pro Wed Oct 24 16:37:58 2007 +0000 @@ -0,0 +1,23 @@ +TEMPLATE = lib + +SV_UNIT_PACKAGES = +load(../sv.prf) + +CONFIG += sv staticlib qt thread warn_on stl rtti exceptions +QT += xml + +TARGET = svdocument + +DEPENDPATH += .. +INCLUDEPATH += . .. +OBJECTS_DIR = tmp_obj +MOC_DIR = tmp_moc + +HEADERS += Document.h \ + MainWindowBase.h \ + SVFileReader.h + +SOURCES += Document.cpp \ + MainWindowBase.cpp \ + SVFileReader.cpp +