/*
    Rosegarden
    A MIDI and audio sequencer and musical notation editor.
 
    This program is Copyright 2000-2008
        Guillaume Laurent   <glaurent@telegraph-road.org>,
        Chris Cannam        <cannam@all-day-breakfast.com>,
        Richard Bown        <richard.bown@ferventsoftware.com>
 
    The moral rights of Guillaume Laurent, Chris Cannam, and Richard
    Bown to claim authorship of this work have been asserted.
 
    Other copyrights also apply to some parts of this work.  Please
    see the AUTHORS file and individual file headers for details.
 
    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 "RosegardenGUIDoc.h"
#include <tdeapplication.h>

#include <tqxml.h>
#include "sound/Midi.h"
#include "gui/editors/segment/TrackEditor.h"
#include "gui/editors/segment/TrackButtons.h"
#include <tdelocale.h>
#include <kstandarddirs.h>
#include "misc/Debug.h"
#include "misc/Strings.h"
#include "gui/general/ClefIndex.h"
#include "document/ConfigGroups.h"
#include "base/AudioDevice.h"
#include "base/AudioPluginInstance.h"
#include "base/BaseProperties.h"
#include "base/Clipboard.h"
#include "base/Composition.h"
#include "base/Configuration.h"
#include "base/Device.h"
#include "base/Event.h"
#include "base/Exception.h"
#include "base/Instrument.h"
#include "base/MidiDevice.h"
#include "base/MidiProgram.h"
#include "base/MidiTypes.h"
#include "base/NotationTypes.h"
#include "base/Profiler.h"
#include "base/RealTime.h"
#include "base/Segment.h"
#include "base/SoftSynthDevice.h"
#include "base/Studio.h"
#include "base/Track.h"
#include "base/XmlExportable.h"
#include "commands/edit/EventQuantizeCommand.h"
#include "commands/notation/NormalizeRestsCommand.h"
#include "commands/segment/AddTracksCommand.h"
#include "commands/segment/SegmentInsertCommand.h"
#include "commands/segment/SegmentRecordCommand.h"
#include "commands/segment/ChangeCompositionLengthCommand.h"
#include "gui/application/RosegardenApplication.h"
#include "gui/application/RosegardenGUIApp.h"
#include "gui/application/RosegardenGUIView.h"
#include "gui/dialogs/UnusedAudioSelectionDialog.h"
#include "gui/editors/segment/segmentcanvas/AudioPreviewThread.h"
#include "gui/editors/segment/TrackLabel.h"
#include "gui/general/EditViewBase.h"
#include "gui/general/GUIPalette.h"
#include "gui/kdeext/TDEStartupLogo.h"
#include "gui/seqmanager/SequenceManager.h"
#include "gui/studio/AudioPluginManager.h"
#include "gui/studio/StudioControl.h"
#include "gui/widgets/CurrentProgressDialog.h"
#include "gui/widgets/ProgressDialog.h"
#include "MultiViewCommandHistory.h"
#include "RoseXmlHandler.h"
#include "sound/AudioFile.h"
#include "sound/AudioFileManager.h"
#include "sound/MappedCommon.h"
#include "sound/MappedComposition.h"
#include "sound/MappedDevice.h"
#include "sound/MappedInstrument.h"
#include "sound/MappedEvent.h"
#include "sound/MappedRealTime.h"
#include "sound/MappedStudio.h"
#include "sound/PluginIdentifier.h"
#include "sound/SoundDriver.h"
#include <kcommand.h>
#include <tdeconfig.h>
#include <kfilterdev.h>
#include <tdeglobal.h>
#include <tdemessagebox.h>
#include <kprocess.h>
#include <kprogress.h>
#include <tdetempfile.h>
#include <tqcstring.h>
#include <tqdatastream.h>
#include <tqdialog.h>
#include <tqdir.h>
#include <tqfile.h>
#include <tqfileinfo.h>
#include <tqobject.h>
#include <tqstring.h>
#include <tqstringlist.h>
#include <tqtextstream.h>
#include <tqwidget.h>
#include "gui/widgets/ProgressBar.h"


namespace Rosegarden
{

using namespace BaseProperties;

RosegardenGUIDoc::RosegardenGUIDoc(TQWidget *parent,
                                   AudioPluginManager *pluginManager,
                                   bool skipAutoload,
                                   const char *name)
        : TQObject(parent, name),
        m_modified(false),
        m_autoSaved(false),
        m_audioPreviewThread(&m_audioFileManager),
        m_commandHistory(new MultiViewCommandHistory()),
        m_pluginManager(pluginManager),
        m_audioRecordLatency(0, 0),
        m_autoSavePeriod(0),
        m_quickMarkerTime(-1),
        m_beingDestroyed(false)
{
    syncDevices();

    m_viewList.setAutoDelete(false);
    m_editViewList.setAutoDelete(false);

    connect(m_commandHistory, TQ_SIGNAL(commandExecuted(KCommand *)),
            this, TQ_SLOT(slotDocumentModified()));

    connect(m_commandHistory, TQ_SIGNAL(documentRestored()),
            this, TQ_SLOT(slotDocumentRestored()));

    // autoload a new document
    if (!skipAutoload)
        performAutoload();

    // now set it up as a "new document"
    newDocument();
}

RosegardenGUIDoc::~RosegardenGUIDoc()
{
    RG_DEBUG << "~RosegardenGUIDoc()\n";
    m_beingDestroyed = true;

    m_audioPreviewThread.finish();
    m_audioPreviewThread.wait();

    deleteEditViews();

    //     ControlRulerCanvasRepository::clear();

    delete m_commandHistory; // must be deleted before the Composition is
}

unsigned int
RosegardenGUIDoc::getAutoSavePeriod() const
{
    TDEConfig* config = kapp->config();
    config->setGroup(GeneralOptionsConfigGroup);
    return config->readUnsignedNumEntry("autosaveinterval", 60);
}

void RosegardenGUIDoc::attachView(RosegardenGUIView *view)
{
    m_viewList.append(view);
}

void RosegardenGUIDoc::detachView(RosegardenGUIView *view)
{
    m_viewList.remove(view);
}

void RosegardenGUIDoc::attachEditView(EditViewBase *view)
{
    m_editViewList.append(view);
}

void RosegardenGUIDoc::detachEditView(EditViewBase *view)
{
    // auto-deletion is disabled, as
    // the editview detaches itself when being deleted
    m_editViewList.remove(view);
}

void RosegardenGUIDoc::deleteEditViews()
{
    // enabled auto-deletion : edit views will be deleted
    m_editViewList.setAutoDelete(true);
    m_editViewList.clear();
}

void RosegardenGUIDoc::setAbsFilePath(const TQString &filename)
{
    m_absFilePath = filename;
}

void RosegardenGUIDoc::setTitle(const TQString &_t)
{
    m_title = _t;
}

const TQString &RosegardenGUIDoc::getAbsFilePath() const
{
    return m_absFilePath;
}

const TQString& RosegardenGUIDoc::getTitle() const
{
    return m_title;
}

void RosegardenGUIDoc::slotUpdateAllViews(RosegardenGUIView *sender)
{
    RosegardenGUIView *w;

    for (w = m_viewList.first(); w != 0; w = m_viewList.next()) {
        if (w != sender)
            w->repaint();
    }
}

void RosegardenGUIDoc::setModified(bool m)
{
    m_modified = m;
    RG_DEBUG << "RosegardenGUIDoc[" << this << "]::setModified(" << m << ")\n";
}

void RosegardenGUIDoc::clearModifiedStatus()
{
    setModified(false);
    setAutoSaved(true);
    emit documentModified(false);
}

void RosegardenGUIDoc::slotDocumentModified()
{
    RG_DEBUG << "RosegardenGUIDoc::slotDocumentModified()" << endl;
    setModified(true);
    setAutoSaved(false);
    emit documentModified(true);
}

void RosegardenGUIDoc::slotDocumentRestored()
{
    RG_DEBUG << "RosegardenGUIDoc::slotDocumentRestored()\n";
    setModified(false);
}

void
RosegardenGUIDoc::setQuickMarker()
{
    RG_DEBUG << "RosegardenGUIDoc::setQuickMarker" << endl;
    
    m_quickMarkerTime = getComposition().getPosition();
}

void
RosegardenGUIDoc::jumpToQuickMarker()
{
    RG_DEBUG << "RosegardenGUIDoc::jumpToQuickMarker" << endl;

    if (m_quickMarkerTime >= 0)
        slotSetPointerPosition(m_quickMarkerTime);
}

TQString RosegardenGUIDoc::getAutoSaveFileName()
{
    TQString filename = getAbsFilePath();
    if (filename.isEmpty())
        filename = TQDir::currentDirPath() + "/" + getTitle();

    TQString autoSaveFileName = kapp->tempSaveName(filename);

    return autoSaveFileName;
}

void RosegardenGUIDoc::slotAutoSave()
{
    //     RG_DEBUG << "RosegardenGUIDoc::slotAutoSave()\n" << endl;

    if (isAutoSaved() || !isModified())
        return ;

    TQString autoSaveFileName = getAutoSaveFileName();

    RG_DEBUG << "RosegardenGUIDoc::slotAutoSave() - doc modified - saving '"
    << getAbsFilePath() << "' as "
    << autoSaveFileName << endl;

    TQString errMsg;

    saveDocument(autoSaveFileName, errMsg, true);

}

bool RosegardenGUIDoc::isRegularDotRGFile()
{
    return getAbsFilePath().right(3).lower() == ".rg";
}

bool RosegardenGUIDoc::saveIfModified()
{
    RG_DEBUG << "RosegardenGUIDoc::saveIfModified()" << endl;
    bool completed = true;

    if (!isModified())
        return completed;


    RosegardenGUIApp *win = (RosegardenGUIApp *)parent();

    int wantSave = KMessageBox::warningYesNoCancel
                   (win,
                    i18n("The current file has been modified.\n"
                         "Do you want to save it?"),
                    i18n("Warning"));

    RG_DEBUG << "wantSave = " << wantSave << endl;

    switch (wantSave) {

    case KMessageBox::Yes:

        if (!isRegularDotRGFile()) {

            RG_DEBUG << "RosegardenGUIDoc::saveIfModified() : new or imported file\n";
            completed = win->slotFileSaveAs();

        } else {

            RG_DEBUG << "RosegardenGUIDoc::saveIfModified() : regular file\n";
            TQString errMsg;
            completed = saveDocument(getAbsFilePath(), errMsg);

            if (!completed) {
                if (!errMsg.isNull()) {
                    KMessageBox::error(0, i18n(TQString("Could not save document at %1\n(%2)")
                                               .arg(getAbsFilePath()).arg(errMsg).ascii()));
                } else {
                    KMessageBox::error(0, i18n(TQString("Could not save document at %1")
                                               .arg(getAbsFilePath()).ascii()));
                }
            }
        }

        break;

    case KMessageBox::No:
        // delete the autosave file so it won't annoy
        // the user when reloading the file.
        TQFile::remove
            (getAutoSaveFileName());
        completed = true;
        break;

    case KMessageBox::Cancel:
        completed = false;
        break;

    default:
        completed = false;
        break;
    }

    if (completed) {
        completed = deleteOrphanedAudioFiles(wantSave == KMessageBox::No);
        if (completed) {
            m_audioFileManager.resetRecentlyCreatedFiles();
        }
    }

    if (completed)
        setModified(false);
    return completed;
}

bool
RosegardenGUIDoc::deleteOrphanedAudioFiles(bool documentWillNotBeSaved)
{
    std::vector<TQString> recordedOrphans;
    std::vector<TQString> derivedOrphans;

    if (documentWillNotBeSaved) {

        // All audio files recorded or derived in this session are
        // about to become orphans

        for (std::vector<AudioFile *>::const_iterator i =
                    m_audioFileManager.begin();
                i != m_audioFileManager.end(); ++i) {

            if (m_audioFileManager.wasAudioFileRecentlyRecorded((*i)->getId())) {
                recordedOrphans.push_back(strtoqstr((*i)->getFilename()));
            }

            if (m_audioFileManager.wasAudioFileRecentlyDerived((*i)->getId())) {
                derivedOrphans.push_back(strtoqstr((*i)->getFilename()));
            }
        }
    }

    // Whether we save or not, explicitly orphaned (i.e. recorded in
    // this session and then unloaded) recorded files are orphans.
    // Make sure they are actually unknown to the audio file manager
    // (i.e. they haven't been loaded more than once, or reloaded
    // after orphaning).

    for (std::vector<TQString>::iterator i = m_orphanedRecordedAudioFiles.begin();
            i != m_orphanedRecordedAudioFiles.end(); ++i) {

        bool stillHave = false;

        for (std::vector<AudioFile *>::const_iterator j =
                 m_audioFileManager.begin();
                j != m_audioFileManager.end(); ++j) {
            if (strtoqstr((*j)->getFilename()) == *i) {
                stillHave = true;
                break;
            }
        }

        if (!stillHave) recordedOrphans.push_back(*i);
    }

    // Derived orphans get deleted whatever happens
    //!!! Should we orphan any file derived during this session that
    //is not currently used in a segment?  Probably: we have no way to
    //reuse them

    for (std::vector<TQString>::iterator i = m_orphanedDerivedAudioFiles.begin();
            i != m_orphanedDerivedAudioFiles.end(); ++i) {

        bool stillHave = false;

        for (std::vector<AudioFile *>::const_iterator j =
                 m_audioFileManager.begin();
                j != m_audioFileManager.end(); ++j) {
            if (strtoqstr((*j)->getFilename()) == *i) {
                stillHave = true;
                break;
            }
        }

        if (!stillHave) derivedOrphans.push_back(*i);
    }

    for (size_t i = 0; i < derivedOrphans.size(); ++i) {
        TQFile file(derivedOrphans[i]);
        if (!file.remove()) {
            std::cerr << "WARNING: Failed to remove orphaned derived audio file \"" << derivedOrphans[i].ascii() << std::endl;
        }
        TQFile peakFile(TQString("%1.pk").arg(derivedOrphans[i]));
        peakFile.remove();
    }

    m_orphanedDerivedAudioFiles.clear();

    if (recordedOrphans.empty())
        return true;

    if (documentWillNotBeSaved) {

        int reply = KMessageBox::warningYesNoCancel
                    (0,
                     i18n("Delete the 1 audio file recorded during the unsaved session?",
                          "Delete the %n audio files recorded during the unsaved session?",
                          recordedOrphans.size()));

        switch (reply) {

        case KMessageBox::Yes:
            break;

        case KMessageBox::No:
            return true;

        default:
        case KMessageBox::Cancel:
            return false;
        }

    } else {

        UnusedAudioSelectionDialog *dialog =
            new UnusedAudioSelectionDialog
            (0,
             i18n("The following audio files were recorded during this session but have been unloaded\nfrom the audio file manager, and so are no longer in use in the document you are saving.\n\nYou may want to clean up these files to save disk space.\n\nPlease select any you wish to delete permanently from the hard disk.\n"),
             recordedOrphans);

        if (dialog->exec() != TQDialog::Accepted) {
            delete dialog;
            return true;
        }

        recordedOrphans = dialog->getSelectedAudioFileNames();
        delete dialog;
    }

    if (recordedOrphans.empty())
        return true;

    TQString question =
        i18n("<qt>About to delete 1 audio file permanently from the hard disk.<br>There will be no way to recover this file.<br>Are you sure?</qt>\n", "<qt>About to delete %n audio files permanently from the hard disk.<br>There will be no way to recover these files.<br>Are you sure?</qt>", recordedOrphans.size());

    int reply = KMessageBox::warningContinueCancel(0, question);

    if (reply == KMessageBox::Continue) {
        for (size_t i = 0; i < recordedOrphans.size(); ++i) {
            TQFile file(recordedOrphans[i]);
            if (!file.remove()) {
                KMessageBox::error(0, i18n("File %1 could not be deleted.")
                                   .arg(recordedOrphans[i]));
            }

            TQFile peakFile(TQString("%1.pk").arg(recordedOrphans[i]));
            peakFile.remove();
        }
    }

    return true;
}

void RosegardenGUIDoc::newDocument()
{
    setModified(false);
    setAbsFilePath(TQString());
    setTitle(i18n("Untitled"));
    m_commandHistory->clear();
}

void RosegardenGUIDoc::performAutoload()
{
    TQString autoloadFile =
        TDEGlobal::dirs()->findResource("appdata", "autoload.rg");

    TQFileInfo autoloadFileInfo(autoloadFile);

    if (!autoloadFileInfo.isReadable()) {
        RG_DEBUG << "RosegardenGUIDoc::performAutoload - "
        << "can't find autoload file - defaulting" << endl;
        return ;
    }

    openDocument(autoloadFile);

}

bool RosegardenGUIDoc::openDocument(const TQString& filename,
                                    bool permanent,
                                    const char* /*format*/ /*=0*/)
{
    RG_DEBUG << "RosegardenGUIDoc::openDocument("
    << filename << ")" << endl;

    if (!filename || filename.isEmpty())
        return false;

    newDocument();

    TQFileInfo fileInfo(filename);
    setTitle(fileInfo.fileName());

    // Check if file readable with fileInfo ?
    if (!fileInfo.isReadable() || fileInfo.isDir()) {
        TDEStartupLogo::hideIfStillThere();
        TQString msg(i18n("Can't open file '%1'").arg(filename));
        KMessageBox::sorry(0, msg);
        return false;
    }

    ProgressDialog progressDlg(i18n("Reading file..."),
                               100,
                               (TQWidget*)parent());

    connect(&progressDlg, TQ_SIGNAL(cancelClicked()),
            &m_audioFileManager, TQ_SLOT(slotStopPreview()));

    progressDlg.setMinimumDuration(500);
    progressDlg.setAutoReset(true); // we're re-using it for the preview generation
    setAbsFilePath(fileInfo.absFilePath());

    TQString errMsg;
    TQString fileContents;
    bool cancelled = false, okay = true;

    KFilterDev* fileCompressedDevice = static_cast<KFilterDev*>(KFilterDev::deviceForFile(filename, "application/x-gzip"));
    if (fileCompressedDevice == 0) {

        errMsg = i18n("Could not open Rosegarden file");

    } else {
        fileCompressedDevice->open(IO_ReadOnly);

        unsigned int elementCount = fileInfo.size() / 4; // approx. guess
        //         RG_DEBUG << "RosegardenGUIDoc::xmlParse() : elementCount = " << elementCount
        //                  << " - file size : " << file->size()
        //                  << endl;


        // Fugly work-around in case of broken rg files
        //
        int c = 0;
        std::vector<char> baseBuffer;

        while (c != -1) {
            c = fileCompressedDevice->getch();
            if (c != -1)
                baseBuffer.push_back(c);
        }

        fileCompressedDevice->close();

        TQString fileContents = TQString::fromUtf8(&baseBuffer[0],
                               baseBuffer.size());

        // parse xml file
        okay = xmlParse(fileContents, errMsg, &progressDlg,
                        elementCount, permanent, cancelled);
        // 	okay = xmlParse(fileCompressedDevice, errMsg, &progressDlg,
        //                         elementCount, permanent, cancelled);
        delete fileCompressedDevice;

    }

    if (!okay) {
        TDEStartupLogo::hideIfStillThere();
        TQString msg(i18n("Error when parsing file '%1': \"%2\"")
                    .arg(filename)
                    .arg(errMsg));

        CurrentProgressDialog::freeze();
        KMessageBox::sorry(0, msg);
        CurrentProgressDialog::thaw();

        return false;

    } else if (cancelled) {
        newDocument();
        return false;
    }

    RG_DEBUG << "RosegardenGUIDoc::openDocument() end - "
    << "m_composition : " << &m_composition
    << " - m_composition->getNbSegments() : "
    << m_composition.getNbSegments()
    << " - m_composition->getDuration() : "
    << m_composition.getDuration() << endl;

    if (m_composition.begin() != m_composition.end()) {
        RG_DEBUG << "First segment starts at " << (*m_composition.begin())->getStartTime() << endl;
    }

    // Ensure a minimum of 64 tracks
    //
    //     unsigned int nbTracks = m_composition.getNbTracks();
    //     TrackId maxTrackId = m_composition.getMaxTrackId();
    //     InstrumentId instBase = MidiInstrumentBase;

    //     for(unsigned int i = nbTracks; i < MinNbOfTracks; ++i) {

    //         Track *track;

    //         track = new Track(maxTrackId + 1,          // id
    //                                       (i + instBase) % 16,     // instrument
    //                                       i,                       // position
    //                                       "untitled",
    //                                       false);                  // mute

    //         m_composition.addTrack(track);
    //         ++maxTrackId;
    //     }

    // We might need a progress dialog when we generate previews,
    // reuse the previous one
    progressDlg.setLabel(i18n("Generating audio previews..."));

    connect(&m_audioFileManager, TQ_SIGNAL(setProgress(int)),
            progressDlg.progressBar(), TQ_SLOT(setValue(int)));
    try {
        // generate any audio previews after loading the files
        m_audioFileManager.generatePreviews();
    } catch (Exception e) {
        TDEStartupLogo::hideIfStillThere();
        CurrentProgressDialog::freeze();
        KMessageBox::error(0, strtoqstr(e.getMessage()));
        CurrentProgressDialog::thaw();
    }

    if (isSequencerRunning()) {
        // Initialise the whole studio - faders, plugins etc.
        //
        initialiseStudio();

        // Initialise the MIDI controllers (reaches through to MIDI devices
        // to set them up)
        //
        initialiseControllers();
    }

    return true;
}

void
RosegardenGUIDoc::mergeDocument(RosegardenGUIDoc *doc,
                                int options)
{
    KMacroCommand *command = new KMacroCommand(i18n("Merge"));

    timeT time0 = 0;
    if (options & MERGE_AT_END) {
        time0 = getComposition().getBarEndForTime(getComposition().getDuration());
    }

    int myMaxTrack = getComposition().getNbTracks();
    int yrMinTrack = 0;
    int yrMaxTrack = doc->getComposition().getNbTracks();
    int yrNrTracks = yrMaxTrack - yrMinTrack + 1;

    int firstAlteredTrack = yrMinTrack;

    if (options & MERGE_IN_NEW_TRACKS) {

        //!!! worry about instruments and other studio stuff later... if at all
        command->addCommand(new AddTracksCommand
                            (&getComposition(),
                             yrNrTracks,
                             MidiInstrumentBase,
                             -1));

        firstAlteredTrack = myMaxTrack + 1;

    } else if (yrMaxTrack > myMaxTrack) {

        command->addCommand(new AddTracksCommand
                            (&getComposition(),
                             yrMaxTrack - myMaxTrack,
                             MidiInstrumentBase,
                             -1));
    }

    TrackId firstNewTrackId = getComposition().getNewTrackId();
    timeT lastSegmentEndTime = 0;

    for (Composition::iterator i = doc->getComposition().begin(), j = i;
         i != doc->getComposition().end(); i = j) {

        ++j;
        Segment *s = *i;
        timeT segmentEndTime = s->getEndMarkerTime();

        int yrTrack = s->getTrack();
        Track *t = doc->getComposition().getTrackById(yrTrack);
        if (t) yrTrack = t->getPosition();

        int myTrack = yrTrack;

        if (options & MERGE_IN_NEW_TRACKS) {
            myTrack = yrTrack - yrMinTrack + myMaxTrack + 1;
        }

        doc->getComposition().detachSegment(s);

        if (options & MERGE_AT_END) {
            s->setStartTime(s->getStartTime() + time0);
            segmentEndTime += time0;
        }
        if (segmentEndTime > lastSegmentEndTime) {
            lastSegmentEndTime = segmentEndTime;
        }

        Track *track = getComposition().getTrackByPosition(myTrack);
        TrackId tid = 0;
        if (track) tid = track->getId();
        else tid = firstNewTrackId + yrTrack - yrMinTrack;

        command->addCommand(new SegmentInsertCommand(&getComposition(), s, tid));
    }

    if (!(options & MERGE_KEEP_OLD_TIMINGS)) {
        for (int i = getComposition().getTimeSignatureCount() - 1; i >= 0; --i) {
            getComposition().removeTimeSignature(i);
        }
        for (int i = getComposition().getTempoChangeCount() - 1; i >= 0; --i) {
            getComposition().removeTempoChange(i);
        }
    }

    if (options & MERGE_KEEP_NEW_TIMINGS) {
        for (int i = 0; i < doc->getComposition().getTimeSignatureCount(); ++i) {
            std::pair<timeT, TimeSignature> ts =
                doc->getComposition().getTimeSignatureChange(i);
            getComposition().addTimeSignature(ts.first + time0, ts.second);
        }
        for (int i = 0; i < doc->getComposition().getTempoChangeCount(); ++i) {
            std::pair<timeT, tempoT> t =
                doc->getComposition().getTempoChange(i);
            getComposition().addTempoAtTime(t.first + time0, t.second);
        }
    }

    if (lastSegmentEndTime > getComposition().getEndMarker()) {
        command->addCommand(new ChangeCompositionLengthCommand
                            (&getComposition(),
                             getComposition().getStartMarker(),
                             lastSegmentEndTime));
    }

    m_commandHistory->addCommand(command);

    emit makeTrackVisible(firstAlteredTrack + yrNrTracks/2 + 1);
}

void RosegardenGUIDoc::clearStudio()
{
    TQCString replyType;
    TQByteArray replyData;
    rgapp->sequencerCall("clearStudio()", replyType, replyData);
    RG_DEBUG << "cleared studio\n";
}

void RosegardenGUIDoc::initialiseStudio()
{
    Profiler profiler("initialiseStudio", true);

    RG_DEBUG << "RosegardenGUIDoc::initialiseStudio - "
    << "clearing down and initialising" << endl;

    clearStudio();

    InstrumentList list = m_studio.getAllInstruments();
    InstrumentList::iterator it = list.begin();
    int audioCount = 0;

    BussList busses = m_studio.getBusses();
    RecordInList recordIns = m_studio.getRecordIns();

    // To reduce the number of DCOP calls at this stage, we put some
    // of the float property values in a big list and commit in one
    // single call at the end.  We can only do this with properties
    // that aren't depended on by other port, connection, or non-float
    // properties during the initialisation process.
    MappedObjectIdList ids;
    MappedObjectPropertyList properties;
    MappedObjectValueList values;

    std::vector<PluginContainer *> pluginContainers;

    for (unsigned int i = 0; i < busses.size(); ++i) {

        // first one is master
        MappedObjectId mappedId =
            StudioControl::createStudioObject(
                MappedObject::AudioBuss);

        StudioControl::setStudioObjectProperty
        (mappedId,
         MappedAudioBuss::BussId,
         MappedObjectValue(i));

        ids.push_back(mappedId);
        properties.push_back(MappedAudioBuss::Level);
        values.push_back(MappedObjectValue(busses[i]->getLevel()));

        ids.push_back(mappedId);
        properties.push_back(MappedAudioBuss::Pan);
        values.push_back(MappedObjectValue(busses[i]->getPan()) - 100.0);

        busses[i]->setMappedId(mappedId);

        pluginContainers.push_back(busses[i]);
    }

    for (unsigned int i = 0; i < recordIns.size(); ++i) {

        MappedObjectId mappedId =
            StudioControl::createStudioObject(
                MappedObject::AudioInput);

        StudioControl::setStudioObjectProperty
        (mappedId,
         MappedAudioInput::InputNumber,
         MappedObjectValue(i));

        recordIns[i]->setMappedId(mappedId);
    }

    for (; it != list.end(); it++) {
        if ((*it)->getType() == Instrument::Audio ||
                (*it)->getType() == Instrument::SoftSynth) {
            MappedObjectId mappedId =
                StudioControl::createStudioObject(
                    MappedObject::AudioFader);

            // Set the object id against the instrument
            //
            (*it)->setMappedId(mappedId);

            /*
            cout << "SETTING MAPPED OBJECT ID = " << mappedId
                 << " - on Instrument " << (*it)->getId() << endl;
                 */


            // Set the instrument id against this object
            //
            StudioControl::setStudioObjectProperty
            (mappedId,
             MappedObject::Instrument,
             MappedObjectValue((*it)->getId()));

            // Set the level
            //
            ids.push_back(mappedId);
            properties.push_back(MappedAudioFader::FaderLevel);
            values.push_back(MappedObjectValue((*it)->getLevel()));

            // Set the record level
            //
            ids.push_back(mappedId);
            properties.push_back(MappedAudioFader::FaderRecordLevel);
            values.push_back(MappedObjectValue((*it)->getRecordLevel()));

            // Set the number of channels
            //
            ids.push_back(mappedId);
            properties.push_back(MappedAudioFader::Channels);
            values.push_back(MappedObjectValue((*it)->getAudioChannels()));

            // Set the pan - 0 based
            //
            ids.push_back(mappedId);
            properties.push_back(MappedAudioFader::Pan);
            values.push_back(MappedObjectValue(float((*it)->getPan())) - 100.0);

            // Set up connections: first clear any existing ones (shouldn't
            // be necessary, but)
            //
            StudioControl::disconnectStudioObject(mappedId);

            // then handle the output connection
            //
            BussId outputBuss = (*it)->getAudioOutput();
            if (outputBuss < busses.size()) {
                MappedObjectId bmi = busses[outputBuss]->getMappedId();

                if (bmi > 0) {
                    StudioControl::connectStudioObjects(mappedId, bmi);
                }
            }

            // then the input
            //
            bool isBuss;
            int channel;
            int input = (*it)->getAudioInput(isBuss, channel);
            MappedObjectId rmi = 0;

            if (isBuss) {
                if (input < int(busses.size())) {
                    rmi = busses[input]->getMappedId();
                }
            } else {
                if (input < int(recordIns.size())) {
                    rmi = recordIns[input]->getMappedId();
                }
            }

            ids.push_back(mappedId);
            properties.push_back(MappedAudioFader::InputChannel);
            values.push_back(MappedObjectValue(channel));

            if (rmi > 0) {
                StudioControl::connectStudioObjects(rmi, mappedId);
            }

            pluginContainers.push_back(*it);

            audioCount++;
        }
    }

    for (std::vector<PluginContainer *>::iterator pci =
                pluginContainers.begin(); pci != pluginContainers.end(); ++pci) {

        // Initialise all the plugins for this Instrument or Buss

        for (PluginInstanceIterator pli = (*pci)->beginPlugins();
                pli != (*pci)->endPlugins(); ++pli) {

            AudioPluginInstance *plugin = *pli;

            if (plugin->isAssigned()) {
                // Create the plugin slot at the sequencer Studio
                //
                MappedObjectId pluginMappedId =
                    StudioControl::createStudioObject(
                        MappedObject::PluginSlot);

                // Create the back linkage from the instance to the
                // studio id
                //
                plugin->setMappedId(pluginMappedId);

                //RG_DEBUG << "CREATING PLUGIN ID = "
                //<< pluginMappedId << endl;

                // Set the position
                StudioControl::setStudioObjectProperty
                (pluginMappedId,
                 MappedObject::Position,
                 MappedObjectValue(plugin->getPosition()));

                // Set the id of this instrument or buss on the plugin
                //
                StudioControl::setStudioObjectProperty
                (pluginMappedId,
                 MappedObject::Instrument,
                 (*pci)->getId());

                // Set the plugin type id - this will set it up ready
                // for the rest of the settings.  String value, so can't
                // go in the main property list.
                //
                StudioControl::setStudioObjectProperty
                (pluginMappedId,
                 MappedPluginSlot::Identifier,
                 plugin->getIdentifier().c_str());

                plugin->setConfigurationValue
                (qstrtostr(PluginIdentifier::RESERVED_PROJECT_DIRECTORY_KEY),
                 getAudioFileManager().getAudioPath());

                // Set opaque string configuration data (e.g. for DSSI plugin)
                //
                MappedObjectPropertyList config;
                for (AudioPluginInstance::ConfigMap::const_iterator
                        i = plugin->getConfiguration().begin();
                        i != plugin->getConfiguration().end(); ++i) {
                    config.push_back(strtoqstr(i->first));
                    config.push_back(strtoqstr(i->second));
                }

                StudioControl::setStudioObjectPropertyList
                (pluginMappedId,
                 MappedPluginSlot::Configuration,
                 config);

                // Set the bypass
                //
                ids.push_back(pluginMappedId);
                properties.push_back(MappedPluginSlot::Bypassed);
                values.push_back(MappedObjectValue(plugin->isBypassed()));

                // Set all the port values
                //
                PortInstanceIterator portIt;

                for (portIt = plugin->begin();
                        portIt != plugin->end(); ++portIt) {
                    StudioControl::setStudioPluginPort
                    (pluginMappedId,
                     (*portIt)->number,
                     (*portIt)->value);
                }

                // Set the program
                //
                if (plugin->getProgram() != "") {
                    StudioControl::setStudioObjectProperty
                    (pluginMappedId,
                     MappedPluginSlot::Program,
                     strtoqstr(plugin->getProgram()));
                }

                // Set the post-program port values
                //
                for (portIt = plugin->begin();
                        portIt != plugin->end(); ++portIt) {
                    if ((*portIt)->changedSinceProgramChange) {
                        StudioControl::setStudioPluginPort
                        (pluginMappedId,
                         (*portIt)->number,
                         (*portIt)->value);
                    }
                }
            }
        }
    }

    // Now commit all the remaining changes
    StudioControl::setStudioObjectProperties(ids, properties, values);

    TDEConfig* config = kapp->config();
    config->setGroup(SequencerOptionsConfigGroup);

    bool faderOuts = config->readBoolEntry("audiofaderouts", false);
    bool submasterOuts = config->readBoolEntry("audiosubmasterouts", false);
    unsigned int audioFileFormat = config->readUnsignedNumEntry("audiorecordfileformat", 1);

    MidiByte ports = 0;
    if (faderOuts) {
        ports |= MappedEvent::FaderOuts;
    }
    if (submasterOuts) {
        ports |= MappedEvent::SubmasterOuts;
    }
    MappedEvent mEports
    (MidiInstrumentBase,
     MappedEvent::SystemAudioPorts,
     ports);

    StudioControl::sendMappedEvent(mEports);

    MappedEvent mEff
    (MidiInstrumentBase,
     MappedEvent::SystemAudioFileFormat,
     audioFileFormat);
    StudioControl::sendMappedEvent(mEff);
}

int RosegardenGUIDoc::FILE_FORMAT_VERSION_MAJOR = 1;

int RosegardenGUIDoc::FILE_FORMAT_VERSION_MINOR = 4;

int RosegardenGUIDoc::FILE_FORMAT_VERSION_POINT = 0;



bool RosegardenGUIDoc::saveDocument(const TQString& filename,
                                    TQString& errMsg,
                                    bool autosave)
{
    if (!TQFileInfo(filename).exists()) { // safe to write directly
        return saveDocumentActual(filename, errMsg, autosave);
    }

    KTempFile temp(filename + ".", "", 0644); // will be umask'd

    int status = temp.status();
    if (status != 0) {
        errMsg = i18n(TQString("Could not create temporary file in directory of '%1': %2").arg(filename).arg(strerror(status)).ascii());
        return false;
    }

    TQString tempFileName = temp.name();

    RG_DEBUG << "Temporary file name is: \"" << tempFileName << "\"" << endl;

    // KTempFile creates a temporary file that is already open: close it
    if (!temp.close()) {
        status = temp.status();
        errMsg = i18n(TQString("Failure in temporary file handling for file '%1': %2")
                      .arg(tempFileName).arg(strerror(status)).ascii());
        return false;
    }

    bool success = saveDocumentActual(tempFileName, errMsg, autosave);

    if (!success) {
        // errMsg should be already set
        return false;
    }

    TQDir dir(TQFileInfo(tempFileName).dir());
    if (!dir.rename(tempFileName, filename)) {
        errMsg = i18n(TQString("Failed to rename temporary output file '%1' to desired output file '%2'").arg(tempFileName).arg(filename).ascii());
        return false;
    }

    return true;
}


bool RosegardenGUIDoc::saveDocumentActual(const TQString& filename,
                                          TQString& errMsg,
                                          bool autosave)
{
    Profiler profiler("RosegardenGUIDoc::saveDocumentActual");
    RG_DEBUG << "RosegardenGUIDoc::saveDocumentActual(" << filename << ")\n";

    KFilterDev* fileCompressedDevice = static_cast<KFilterDev*>(KFilterDev::deviceForFile(filename, "application/x-gzip"));
    fileCompressedDevice->setOrigFileName("audio/x-rosegarden");
    bool rc = fileCompressedDevice->open(IO_WriteOnly);

    if (!rc) {
        // do some error report
        errMsg = i18n(TQString("Could not open file '%1' for writing").arg(filename).ascii());
        delete fileCompressedDevice;
        return false; // couldn't open file
    }


    TQTextStream outStream(fileCompressedDevice);
    outStream.setEncoding(TQTextStream::UnicodeUTF8);

    // output XML header
    //
    outStream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
    << "<!DOCTYPE rosegarden-data>\n"
    << "<rosegarden-data version=\"" << VERSION
    << "\" format-version-major=\"" << FILE_FORMAT_VERSION_MAJOR
    << "\" format-version-minor=\"" << FILE_FORMAT_VERSION_MINOR
    << "\" format-version-point=\"" << FILE_FORMAT_VERSION_POINT
    << "\">\n";

    ProgressDialog *progressDlg = 0;
    KProgress *progress = 0;

    if (!autosave) {

        progressDlg = new ProgressDialog(i18n("Saving file..."),
                                         100,
                                         (TQWidget*)parent());
        progress = progressDlg->progressBar();

        progressDlg->setMinimumDuration(500);
        progressDlg->setAutoReset(true);

    } else {

        progress = ((RosegardenGUIApp *)parent())->getProgressBar();
    }

    // Send out Composition (this includes Tracks, Instruments, Tempo
    // and Time Signature changes and any other sub-objects)
    //
    outStream << strtoqstr(getComposition().toXmlString())
    << endl << endl;

    outStream << strtoqstr(getAudioFileManager().toXmlString())
    << endl << endl;

    outStream << strtoqstr(getConfiguration().toXmlString())
    << endl << endl;

    long totalEvents = 0;
    for (Composition::iterator segitr = m_composition.begin();
            segitr != m_composition.end(); ++segitr) {
        totalEvents += (*segitr)->size();
    }

    for (Composition::triggersegmentcontaineriterator ci =
                m_composition.getTriggerSegments().begin();
            ci != m_composition.getTriggerSegments().end(); ++ci) {
        totalEvents += (*ci)->getSegment()->size();
    }

    // output all elements
    //
    // Iterate on segments
    long eventCount = 0;

    for (Composition::iterator segitr = m_composition.begin();
            segitr != m_composition.end(); ++segitr) {

        Segment *segment = *segitr;

        saveSegment(outStream, segment, progress, totalEvents, eventCount);

    }

    // Put a break in the file
    //
    outStream << endl << endl;

    for (Composition::triggersegmentcontaineriterator ci =
                m_composition.getTriggerSegments().begin();
            ci != m_composition.getTriggerSegments().end(); ++ci) {

        TQString triggerAtts = TQString
                              ("triggerid=\"%1\" triggerbasepitch=\"%2\" triggerbasevelocity=\"%3\" triggerretune=\"%4\" triggeradjusttimes=\"%5\" ")
                              .arg((*ci)->getId())
                              .arg((*ci)->getBasePitch())
                              .arg((*ci)->getBaseVelocity())
                              .arg((*ci)->getDefaultRetune())
                              .arg(strtoqstr((*ci)->getDefaultTimeAdjust()));

        Segment *segment = (*ci)->getSegment();
        saveSegment(outStream, segment, progress, totalEvents, eventCount, triggerAtts);
    }

    // Put a break in the file
    //
    outStream << endl << endl;

    // Send out the studio - a self contained command
    //
    outStream << strtoqstr(m_studio.toXmlString()) << endl << endl;


    // Send out the appearance data
    outStream << "<appearance>" << endl;
    outStream << strtoqstr(getComposition().getSegmentColourMap().toXmlString("segmentmap"));
    outStream << strtoqstr(getComposition().getGeneralColourMap().toXmlString("generalmap"));
    outStream << "</appearance>" << endl << endl << endl;

    // close the top-level XML tag
    //
    outStream << "</rosegarden-data>\n";

    // check that all went ok
    //
    if (fileCompressedDevice->status() != IO_Ok) {
        errMsg = i18n(TQString("Error while writing on '%1'").arg(filename).ascii());
        delete fileCompressedDevice;
        return false;
    }

    fileCompressedDevice->close();

    delete fileCompressedDevice; // DO NOT USE outStream AFTER THIS POINT

    RG_DEBUG << endl << "RosegardenGUIDoc::saveDocument() finished\n";

    if (!autosave) {
        emit documentModified(false);
        setModified(false);
        m_commandHistory->documentSaved();
        delete progressDlg;
    } else {
        progress->setProgress(0);
    }

    setAutoSaved(true);

    return true;
}

bool RosegardenGUIDoc::exportStudio(const TQString& filename,
                                    std::vector<DeviceId> devices)
{
    Profiler profiler("RosegardenGUIDoc::exportStudio");
    RG_DEBUG << "RosegardenGUIDoc::exportStudio("
    << filename << ")\n";

    KFilterDev* fileCompressedDevice = static_cast<KFilterDev*>(KFilterDev::deviceForFile(filename, "application/x-gzip"));
    fileCompressedDevice->setOrigFileName("audio/x-rosegarden-device");
    fileCompressedDevice->open(IO_WriteOnly);
    TQTextStream outStream(fileCompressedDevice);
    outStream.setEncoding(TQTextStream::UnicodeUTF8);

    // output XML header
    //
    outStream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
    << "<!DOCTYPE rosegarden-data>\n"
    << "<rosegarden-data version=\"" << VERSION << "\">\n";

    // Send out the studio - a self contained command
    //
    outStream << strtoqstr(m_studio.toXmlString(devices)) << endl << endl;

    // close the top-level XML tag
    //
    outStream << "</rosegarden-data>\n";

    delete fileCompressedDevice;

    RG_DEBUG << endl << "RosegardenGUIDoc::exportStudio() finished\n";
    return true;
}

void RosegardenGUIDoc::saveSegment(TQTextStream& outStream, Segment *segment,
                                   KProgress* progress, long totalEvents, long &count,
                                   TQString extraAttributes)
{
    TQString time;

    outStream << TQString("<segment track=\"%1\" start=\"%2\" ")
    .arg(segment->getTrack())
    .arg(segment->getStartTime());

    if (!extraAttributes.isNull())
        outStream << extraAttributes << " ";

    outStream << "label=\"" <<
    strtoqstr(XmlExportable::encode(segment->getLabel()));

    if (segment->isRepeating()) {
        outStream << "\" repeat=\"true";
    }

    if (segment->getTranspose() != 0) {
        outStream << "\" transpose=\"" << segment->getTranspose();
    }

    if (segment->getDelay() != 0) {
        outStream << "\" delay=\"" << segment->getDelay();
    }

    if (segment->getRealTimeDelay() != RealTime::zeroTime) {
        outStream << "\" rtdelaysec=\"" << segment->getRealTimeDelay().sec
        << "\" rtdelaynsec=\"" << segment->getRealTimeDelay().nsec;
    }

    if (segment->getColourIndex() != 0) {
        outStream << "\" colourindex=\"" << segment->getColourIndex();
    }

    if (segment->getSnapGridSize() != -1) {
        outStream << "\" snapgridsize=\"" << segment->getSnapGridSize();
    }

    if (segment->getViewFeatures() != 0) {
        outStream << "\" viewfeatures=\"" << segment->getViewFeatures();
    }

    const timeT *endMarker = segment->getRawEndMarkerTime();
    if (endMarker) {
        outStream << "\" endmarker=\"" << *endMarker;
    }

    if (segment->getType() == Segment::Audio) {

        outStream << "\" type=\"audio\" "
                  << "file=\""
                  << segment->getAudioFileId();

        if (segment->getStretchRatio() != 1.f &&
            segment->getStretchRatio() != 0.f) {

            outStream << "\" unstretched=\""
                      << segment->getUnstretchedFileId()
                      << "\" stretch=\""
                      << segment->getStretchRatio();
        }
        
        outStream << "\">\n";

        // convert out - should do this as XmlExportable really
        // once all this code is centralised
        //
        time.sprintf("%d.%06d", segment->getAudioStartTime().sec,
                     segment->getAudioStartTime().usec());

        outStream << "    <begin index=\""
        << time
        << "\"/>\n";

        time.sprintf("%d.%06d", segment->getAudioEndTime().sec,
                     segment->getAudioEndTime().usec());

        outStream << "    <end index=\""
        << time
        << "\"/>\n";

        if (segment->isAutoFading()) {
            time.sprintf("%d.%06d", segment->getFadeInTime().sec,
                         segment->getFadeInTime().usec());

            outStream << "    <fadein time=\""
            << time
            << "\"/>\n";

            time.sprintf("%d.%06d", segment->getFadeOutTime().sec,
                         segment->getFadeOutTime().usec());

            outStream << "    <fadeout time=\""
            << time
            << "\"/>\n";
        }

    } else // Internal type
    {
        outStream << "\">\n";

        bool inChord = false;
        timeT chordStart = 0, chordDuration = 0;
        timeT expectedTime = segment->getStartTime();

        for (Segment::iterator i = segment->begin();
                i != segment->end(); ++i) {

            timeT absTime = (*i)->getAbsoluteTime();

            Segment::iterator nextEl = i;
            ++nextEl;

            if (nextEl != segment->end() &&
                    (*nextEl)->getAbsoluteTime() == absTime &&
                    (*i)->getDuration() != 0 &&
                    !inChord) {
                outStream << "<chord>" << endl;
                inChord = true;
                chordStart = absTime;
                chordDuration = 0;
            }

            if (inChord && (*i)->getDuration() > 0)
                if (chordDuration == 0 || (*i)->getDuration() < chordDuration)
                    chordDuration = (*i)->getDuration();

            outStream << '\t'
            << strtoqstr((*i)->toXmlString(expectedTime)) << endl;

            if (nextEl != segment->end() &&
                    (*nextEl)->getAbsoluteTime() != absTime &&
                    inChord) {
                outStream << "</chord>\n";
                inChord = false;
                expectedTime = chordStart + chordDuration;
            } else if (inChord) {
                expectedTime = absTime;
            } else {
                expectedTime = absTime + (*i)->getDuration();
            }

            if ((++count % 500 == 0) && progress) {
                progress->setValue(count * 100 / totalEvents);
            }
        }

        if (inChord) {
            outStream << "</chord>\n";
        }

        // Add EventRulers to segment - we call them controllers because of
        // a historical mistake in naming them.  My bad.  RWB.
        //
        Segment::EventRulerList list = segment->getEventRulerList();

        if (list.size()) {
            outStream << "<gui>\n"; // gui elements
            Segment::EventRulerListConstIterator it;
            for (it = list.begin(); it != list.end(); ++it) {
                outStream << "  <controller type=\"" << strtoqstr((*it)->m_type);

                if ((*it)->m_type == Controller::EventType) {
                    outStream << "\" value =\"" << (*it)->m_controllerValue;
                }

                outStream << "\"/>\n";
            }
            outStream << "</gui>\n";
        }

    }


    outStream << "</segment>\n"; //-------------------------

}

bool RosegardenGUIDoc::isSequencerRunning()
{
    RosegardenGUIApp* parentApp = dynamic_cast<RosegardenGUIApp*>(parent());
    if (!parentApp) {
        RG_DEBUG << "RosegardenGUIDoc::isSequencerRunning() : parentApp == 0\n";
        return false;
    }

    return parentApp->isSequencerRunning();
}

bool
RosegardenGUIDoc::xmlParse(TQString fileContents, TQString &errMsg,
                           ProgressDialog *progress,
                           unsigned int elementCount,
                           bool permanent,
                           bool &cancelled)
{
    cancelled = false;

    RoseXmlHandler handler(this, elementCount, permanent);

    if (progress) {
        connect(&handler, TQ_SIGNAL(setProgress(int)),
                progress->progressBar(), TQ_SLOT(setValue(int)));
        connect(&handler, TQ_SIGNAL(setOperationName(TQString)),
                progress, TQ_SLOT(slotSetOperationName(TQString)));
        connect(&handler, TQ_SIGNAL(incrementProgress(int)),
                progress->progressBar(), TQ_SLOT(advance(int)));
        connect(progress, TQ_SIGNAL(cancelClicked()),
                &handler, TQ_SLOT(slotCancel()));
    }

    TQXmlInputSource source;
    source.setData(fileContents);
    TQXmlSimpleReader reader;
    reader.setContentHandler(&handler);
    reader.setErrorHandler(&handler);

    START_TIMING;
    bool ok = reader.parse(source);
    PRINT_ELAPSED("RosegardenGUIDoc::xmlParse (reader.parse())");

    if (!ok) {

        if (handler.isCancelled()) {
            RG_DEBUG << "File load cancelled\n";
            TDEStartupLogo::hideIfStillThere();
            KMessageBox::information(0, i18n("File load cancelled"));
            cancelled = true;
            return true;
        } else {
            errMsg = handler.errorString();
        }

    } else {

        if (getSequenceManager() &&
            !(getSequenceManager()->getSoundDriverStatus() & AUDIO_OK)) {

            TDEStartupLogo::hideIfStillThere();
            CurrentProgressDialog::freeze();

            if (handler.hasActiveAudio() ||
                (m_pluginManager && !handler.pluginsNotFound().empty())) {

#ifdef HAVE_LIBJACK
                KMessageBox::information
                    (0, i18n("<h3>Audio and plugins not available</h3><p>This composition uses audio files or plugins, but Rosegarden is currently running without audio because the JACK audio server was not available on startup.</p><p>Please exit Rosegarden, start the JACK audio server and re-start Rosegarden if you wish to load this complete composition.</p><p><b>WARNING:</b> If you re-save this composition, all audio and plugin data and settings in it will be lost.</p>"));
#else
                KMessageBox::information
                    (0, i18n("<h3>Audio and plugins not available</h3><p>This composition uses audio files or plugins, but you are running a version of Rosegarden that was compiled without audio support.</p><p><b>WARNING:</b> If you re-save this composition from this version of Rosegarden, all audio and plugin data and settings in it will be lost.</p>"));
#endif
            }
            CurrentProgressDialog::thaw();

        } else {
           
            bool shownWarning = false;

            int sr = 0;
            if (getSequenceManager()) {
                sr = getSequenceManager()->getSampleRate();
            }

            int er = m_audioFileManager.getExpectedSampleRate();

            std::set<int> rates = m_audioFileManager.getActualSampleRates();
            bool other = false;
            bool mixed = (rates.size() > 1);
            for (std::set<int>::iterator i = rates.begin();
                 i != rates.end(); ++i) {
                if (*i != sr) {
                    other = true;
                    break;
                }
            }
                
            if (sr != 0 &&
                handler.hasActiveAudio() &&
                ((er != 0 && er != sr) ||
                 (other && !mixed))) {

                if (er == 0) er = *rates.begin();

                TDEStartupLogo::hideIfStillThere();
                CurrentProgressDialog::freeze();

                KMessageBox::information(0, i18n("<h3>Incorrect audio sample rate</h3><p>This composition contains audio files that were recorded or imported with the audio server running at a different sample rate (%1 Hz) from the current JACK server sample rate (%2 Hz).</p><p>Rosegarden will play this composition at the correct speed, but any audio files in it will probably sound awful.</p><p>Please consider re-starting the JACK server at the correct rate (%3 Hz) and re-loading this composition before you do any more work with it.</p>").arg(er).arg(sr).arg(er));

                CurrentProgressDialog::thaw();
                shownWarning = true;
 
            } else if (sr != 0 && mixed) {
                    
                TDEStartupLogo::hideIfStillThere();
                CurrentProgressDialog::freeze();
                
                KMessageBox::information(0, i18n("<h3>Inconsistent audio sample rates</h3><p>This composition contains audio files at more than one sample rate.</p><p>Rosegarden will play them at the correct speed, but any audio files that were recorded or imported at rates different from the current JACK server sample rate (%1 Hz) will probably sound awful.</p><p>Please see the audio file manager dialog for more details, and consider resampling any files that are at the wrong rate.</p>").arg(sr),
                                         i18n("Inconsistent sample rates"),
                                         "file-load-inconsistent-samplerates");
                    
                CurrentProgressDialog::thaw();
                shownWarning = true;
            }
 
            if (m_pluginManager && !handler.pluginsNotFound().empty()) {

                // We only warn if a plugin manager is present, so as
                // to avoid warnings when importing a studio from
                // another file (which is the normal case in which we
                // have no plugin manager).

                TQString msg(i18n("<h3>Plugins not found</h3><p>The following audio plugins could not be loaded:</p><ul>"));

                for (std::set<TQString>::iterator i = handler.pluginsNotFound().begin();
                     i != handler.pluginsNotFound().end(); ++i) {
                    TQString ident = *i;
                    TQString type, soName, label;
                    PluginIdentifier::parseIdentifier(ident, type, soName, label);
                    TQString pluginFileName = TQFileInfo(soName).fileName();
                    msg += i18n("<li>%1 (from %2)</li>").arg(label).arg(pluginFileName);
                }
                msg += "</ul>";
                
                TDEStartupLogo::hideIfStillThere();
                CurrentProgressDialog::freeze();
                KMessageBox::information(0, msg);
                CurrentProgressDialog::thaw();
                shownWarning = true;
                
            }

            if (handler.isDeprecated() && !shownWarning) {
                
                TQString msg(i18n("This file contains one or more old element types that are now deprecated.\nSupport for these elements may disappear in future versions of Rosegarden.\nWe recommend you re-save this file from this version of Rosegarden to ensure that it can still be re-loaded in future versions."));
                slotDocumentModified(); // so file can be re-saved immediately
                
                TDEStartupLogo::hideIfStillThere();
                CurrentProgressDialog::freeze();
                KMessageBox::information(0, msg);
                CurrentProgressDialog::thaw();
            }
        }
    }

    return ok;
}

void
RosegardenGUIDoc::insertRecordedMidi(const MappedComposition &mC)
{
    RG_DEBUG << "RosegardenGUIDoc::insertRecordedMidi: " << mC.size() << " events" << endl;

    // Just create a new record Segment if we don't have one already.
    // Make sure we don't recreate the record segment if it's already
    // freed.
    //

    //Track *midiRecordTrack = 0;

    const Composition::recordtrackcontainer &tr =
        getComposition().getRecordTracks();

    bool haveMIDIRecordTrack = false;

    for (Composition::recordtrackcontainer::const_iterator i =
                tr.begin(); i != tr.end(); ++i) {
        TrackId tid = (*i);
        Track *track = getComposition().getTrackById(tid);
        if (track) {
            Instrument *instrument =
                m_studio.getInstrumentById(track->getInstrument());
            if (instrument->getType() == Instrument::Midi ||
                    instrument->getType() == Instrument::SoftSynth) {
                haveMIDIRecordTrack = true;
                if (!m_recordMIDISegments[track->getInstrument()]) {
                    addRecordMIDISegment(track->getId());
                }
                break;
            }
        }
    }

    if (!haveMIDIRecordTrack)
        return ;

    if (mC.size() > 0) {
        MappedComposition::const_iterator i;
        Event *rEvent = 0;
        timeT duration, absTime;
        timeT updateFrom = m_composition.getDuration();
        bool haveNotes = false;

        // process all the incoming MappedEvents
        //
        for (i = mC.begin(); i != mC.end(); ++i) {
            if ((*i)->getRecordedDevice() == Device::CONTROL_DEVICE) {
                // send to GUI
                RosegardenGUIView *v;
                for (v = m_viewList.first(); v != 0; v = m_viewList.next()) {
                    v->slotControllerDeviceEventReceived(*i);
                }
                continue;
            }

            absTime = m_composition.getElapsedTimeForRealTime((*i)->getEventTime());

            /* This is incorrect, unless the tempo at absTime happens to
               be the same as the tempo at zero and there are no tempo
               changes within the given duration after either zero or
               absTime

               duration = m_composition.getElapsedTimeForRealTime((*i)->getDuration());
            */
            duration = m_composition.
                       getElapsedTimeForRealTime((*i)->getEventTime() +
                                                 (*i)->getDuration()) - absTime;

            rEvent = 0;
            bool isNoteOn = false;
            int pitch = 0;
            int channel = (*i)->getRecordedChannel();
            int device = (*i)->getRecordedDevice();

	    TrackId tid = (*i)->getTrackId();
	    Track *track = getComposition().getTrackById(tid);

            switch ((*i)->getType()) {
            case MappedEvent::MidiNote:

                // adjust the notation by the opposite of track transpose so the
                // resulting recording will play correctly, and notation will
                // read correctly; tentative fix for #1597279
                pitch = (*i)->getPitch() - track->getTranspose();

                if ((*i)->getDuration() < RealTime::zeroTime) {

                    // it's a note-on; give it a default duration
                    // for insertion into the segment, and make a
                    // mental note to stick it in the note-on map
                    // for when we see the corresponding note-off

                    duration =
                        Note(Note::Crotchet).getDuration();
                    isNoteOn = true;

                    rEvent = new Event(Note::EventType,
                                       absTime,
                                       duration);

                    rEvent->set
                    <Int>(PITCH, pitch);
                    rEvent->set
                    <Int>(VELOCITY, (*i)->getVelocity());

                } else {

                    // it's a note-off

                    //NoteOnMap::iterator mi = m_noteOnEvents.find((*i)->getPitch());
                    PitchMap *pm = &m_noteOnEvents[device][channel];
                    PitchMap::iterator mi = pm->find(pitch);

                    if (mi != pm->end()) {
                        // modify the previously held note-on event,
                        // instead of assigning to rEvent
                        NoteOnRecSet rec_vec = mi->second;
                        Event *oldEv = *rec_vec[0].m_segmentIterator;
                        Event *newEv = new Event
                                       (*oldEv, oldEv->getAbsoluteTime(), duration);

                        newEv->set
                        <Int>(RECORDED_CHANNEL, channel);
                        NoteOnRecSet *replaced =
                            replaceRecordedEvent(rec_vec, newEv);
                        delete replaced;
                        pm->erase(mi);
                        if (updateFrom > newEv->getAbsoluteTime()) {
                            updateFrom = newEv->getAbsoluteTime();
                        }
                        haveNotes = true;
                        delete newEv;
                        // at this point we could quantize the bar if we were
                        // tracking in a notation view
                    } else {
                        std::cerr << " WARNING: NOTE OFF received without corresponding NOTE ON" << std::endl;
                    }
                }

                break;

            case MappedEvent::MidiPitchBend:
                rEvent = PitchBend
                         ((*i)->getData1(), (*i)->getData2()).getAsEvent(absTime);
                rEvent->set
                <Int>(RECORDED_CHANNEL, channel);
                break;

            case MappedEvent::MidiController:
                rEvent = Controller
                         ((*i)->getData1(), (*i)->getData2()).getAsEvent(absTime);
                rEvent->set
                <Int>(RECORDED_CHANNEL, channel);
                break;

            case MappedEvent::MidiProgramChange:
                RG_DEBUG << "RosegardenGUIDoc::insertRecordedMidi()"
                << " - got Program Change (unsupported)"
                << endl;
                break;

            case MappedEvent::MidiKeyPressure:
                rEvent = KeyPressure
                         ((*i)->getData1(), (*i)->getData2()).getAsEvent(absTime);
                rEvent->set
                <Int>(RECORDED_CHANNEL, channel);
                break;

            case MappedEvent::MidiChannelPressure:
                rEvent = ChannelPressure
                         ((*i)->getData1()).getAsEvent(absTime);
                rEvent->set
                <Int>(RECORDED_CHANNEL, channel);
                break;

            case MappedEvent::MidiSystemMessage:
                channel = -1;
                if ((*i)->getData1() == MIDI_SYSTEM_EXCLUSIVE) {
                    rEvent = SystemExclusive
                             (DataBlockRepository::getDataBlockForEvent((*i))).getAsEvent(absTime);
                }

                // Ignore other SystemMessage events for the moment
                //

                break;

            case MappedEvent::MidiNoteOneShot:
                RG_DEBUG << "RosegardenGUIDoc::insertRecordedMidi() - "
                << "GOT UNEXPECTED MappedEvent::MidiNoteOneShot"
                << endl;
                break;

                // Audio control signals - ignore these
            case MappedEvent::Audio:
            case MappedEvent::AudioCancel:
            case MappedEvent::AudioLevel:
            case MappedEvent::AudioStopped:
            case MappedEvent::AudioGeneratePreview:
            case MappedEvent::SystemUpdateInstruments:
                break;

            default:
                RG_DEBUG << "RosegardenGUIDoc::insertRecordedMidi() - "
                << "GOT UNSUPPORTED MAPPED EVENT"
                << endl;
                break;
            }

            // sanity check
            //
            if (rEvent == 0)
                continue;

            // Set the recorded input port
            //
            rEvent->set
            <Int>(RECORDED_PORT, device);

            // Set the proper start index (if we haven't before)
            //
            for ( RecordingSegmentMap::const_iterator it = m_recordMIDISegments.begin();
                    it != m_recordMIDISegments.end(); ++it) {
                Segment *recordMIDISegment = it->second;
                if (recordMIDISegment->size() == 0) {
                    recordMIDISegment->setStartTime (m_composition.getBarStartForTime(absTime));
                    recordMIDISegment->fillWithRests(absTime);
                }
            }

            // Now insert the new event
            //
            insertRecordedEvent(rEvent, device, channel, isNoteOn);
            delete rEvent;
        }

        if (haveNotes) {

            TDEConfig* config = kapp->config();
            config->setGroup(GeneralOptionsConfigGroup);

            int tracking = config->readUnsignedNumEntry("recordtracking", 0);
            if (tracking == 1) { // notation
                for ( RecordingSegmentMap::const_iterator it = m_recordMIDISegments.begin();
                        it != m_recordMIDISegments.end(); ++it) {
                    Segment *recordMIDISegment = it->second;

                    EventQuantizeCommand *command = new EventQuantizeCommand
                                                    (*recordMIDISegment,
                                                     updateFrom,
                                                     recordMIDISegment->getEndTime(),
                                                     "Notation Options",
                                                     true);
                    // don't add to history
                    command->execute();
                }
            }

            // this signal is currently unused - leaving just in case
            // recording segments are updated through the SegmentObserver::eventAdded() interface
            // 	    emit recordMIDISegmentUpdated(m_recordMIDISegment, updateFrom);
        }
    }
}

void
RosegardenGUIDoc::updateRecordingMIDISegment()
{
    //RG_DEBUG << "RosegardenGUIDoc::updateRecordingMIDISegment" << endl;

    if (m_recordMIDISegments.size() == 0) {
        // make this call once to create one
        insertRecordedMidi(MappedComposition());
        if (m_recordMIDISegments.size() == 0)
            return ; // not recording any MIDI
    }

    //RG_DEBUG << "RosegardenGUIDoc::updateRecordingMIDISegment: have record MIDI segment" << endl;

    NoteOnMap tweakedNoteOnEvents;
    for (NoteOnMap::iterator mi = m_noteOnEvents.begin();
            mi != m_noteOnEvents.end(); ++mi)
        for (ChanMap::iterator cm = mi->second.begin();
                cm != mi->second.end(); ++cm)
            for (PitchMap::iterator pm = cm->second.begin();
                    pm != cm->second.end(); ++pm) {

                // anything in the note-on map should be tweaked so as to end
                // at the recording pointer
                NoteOnRecSet rec_vec = pm->second;
                if (rec_vec.size() > 0) {
                    Event *oldEv = *rec_vec[0].m_segmentIterator;
                    Event *newEv = new Event(
                                       *oldEv, oldEv->getAbsoluteTime(),
                                       m_composition.getPosition() - oldEv->getAbsoluteTime() );

                    tweakedNoteOnEvents[mi->first][cm->first][pm->first] =
                        *replaceRecordedEvent(rec_vec, newEv);
                    delete newEv;
                }
            }
    m_noteOnEvents = tweakedNoteOnEvents;
}

RosegardenGUIDoc::NoteOnRecSet *

RosegardenGUIDoc::replaceRecordedEvent(NoteOnRecSet& rec_vec, Event *fresh)
{
    NoteOnRecSet *new_vector = new NoteOnRecSet();
    for ( NoteOnRecSet::const_iterator i = rec_vec.begin(); i != rec_vec.end(); ++i) {
        Segment *recordMIDISegment = i->m_segment;
        recordMIDISegment->erase(i->m_segmentIterator);
        NoteOnRec noteRec;
        noteRec.m_segment = recordMIDISegment;
        noteRec.m_segmentIterator = recordMIDISegment->insert(new Event(*fresh));
        new_vector->push_back(noteRec);
    }
    return new_vector;
}

void
RosegardenGUIDoc::storeNoteOnEvent(Segment *s, Segment::iterator it, int device, int channel)
{
    NoteOnRec record;
    record.m_segment = s;
    record.m_segmentIterator = it;
    int pitch = (*it)->get
                <Int>(PITCH);
    m_noteOnEvents[device][channel][pitch].push_back(record);
}

void
RosegardenGUIDoc::insertRecordedEvent(Event *ev, int device, int channel, bool isNoteOn)
{
    Segment::iterator it;
    for ( RecordingSegmentMap::const_iterator i = m_recordMIDISegments.begin();
            i != m_recordMIDISegments.end(); ++i) {
        Segment *recordMIDISegment = i->second;
        TrackId tid = recordMIDISegment->getTrack();
        Track *track = getComposition().getTrackById(tid);
        if (track) {
            //Instrument *instrument =
            //    m_studio.getInstrumentById(track->getInstrument());
            int chan_filter = track->getMidiInputChannel();
            int dev_filter = track->getMidiInputDevice();
            if (((chan_filter < 0) || (chan_filter == channel)) &&
                    ((dev_filter == int(Device::ALL_DEVICES)) || (dev_filter == device))) {
                it = recordMIDISegment->insert(new Event(*ev));
                if (isNoteOn) {
                    storeNoteOnEvent(recordMIDISegment, it, device, channel);
                }
                RG_DEBUG << "RosegardenGUIDoc::insertRecordedEvent() - matches filter" << endl;
            } else {
                RG_DEBUG << "RosegardenGUIDoc::insertRecordedEvent() - unmatched event discarded" << endl;
            }
        }
    }
}

void
RosegardenGUIDoc::stopRecordingMidi()
{
    RG_DEBUG << "RosegardenGUIDoc::stopRecordingMidi" << endl;

    Composition &c = getComposition();

    timeT endTime = c.getBarEnd(0);

    bool haveMeaning = false;
    timeT earliestMeaning = 0;

    std::vector<RecordingSegmentMap::iterator> toErase;

    for (RecordingSegmentMap::iterator i = m_recordMIDISegments.begin();
         i != m_recordMIDISegments.end();
         ++i) {

        Segment *s = i->second;

        bool meaningless = true;

        for (Segment::iterator j = s->begin(); j != s->end(); ++j) {

            if ((*j)->isa(Clef::EventType)) continue;

            // no rests in the segment yet, so anything else is meaningful
            meaningless = false;

            if (!haveMeaning || (*j)->getAbsoluteTime() < earliestMeaning) {
                earliestMeaning = (*j)->getAbsoluteTime();
            }

            haveMeaning = true;
            break;
        }

        if (meaningless) {
            if (!c.deleteSegment(s)) delete s;
            toErase.push_back(i);
        } else {
            if (endTime < s->getEndTime()) {
                endTime = s->getEndTime();
            }
        }
    }

    for (int i = 0; i < toErase.size(); ++i) {
        m_recordMIDISegments.erase(toErase[i]);
    }

    if (!haveMeaning) return;

    RG_DEBUG << "RosegardenGUIDoc::stopRecordingMidi: have something" << endl;

    // adjust the clef timings so as not to leave a clef stranded at
    // the start of an otherwise empty count-in

    timeT meaningfulBarStart = c.getBarStartForTime(earliestMeaning);
    
    for (RecordingSegmentMap::iterator i = m_recordMIDISegments.begin();
         i != m_recordMIDISegments.end();
         ++i) {

        Segment *s = i->second;
        Segment::iterator j = s->begin();

        if (j == s->end() || !(*j)->isa(Clef::EventType)) continue;

        if ((*j)->getAbsoluteTime() < meaningfulBarStart) {
            Event *e = new Event(**j, meaningfulBarStart);
            s->erase(j);
            s->insert(e);
        }
    }

    for (NoteOnMap::iterator mi = m_noteOnEvents.begin();
         mi != m_noteOnEvents.end(); ++mi) {

        for (ChanMap::iterator cm = mi->second.begin();
             cm != mi->second.end(); ++cm) {

            for (PitchMap::iterator pm = cm->second.begin();
                 pm != cm->second.end(); ++pm) {

                // anything remaining in the note-on map should be
                // made to end at the end of the segment

                NoteOnRecSet rec_vec = pm->second;

                if (rec_vec.size() > 0) {
                    Event *oldEv = *rec_vec[0].m_segmentIterator;
                    Event *newEv = new Event
                        (*oldEv, oldEv->getAbsoluteTime(),
                         endTime - oldEv->getAbsoluteTime());
                    NoteOnRecSet *replaced =
                        replaceRecordedEvent(rec_vec, newEv);
                    delete newEv;
                    delete replaced;
                }
            }
        }
    }
    m_noteOnEvents.clear();

    while (!m_recordMIDISegments.empty()) {

        Segment *s = m_recordMIDISegments.begin()->second;
        m_recordMIDISegments.erase(m_recordMIDISegments.begin());

        // the record segment will have already been added to the
        // composition if there was anything in it; otherwise we don't
        // need to do so

        if (s->getComposition() == 0) {
            delete s;
            continue;
        }

        // Quantize for notation only -- doesn't affect performance timings.
        KMacroCommand *command = new KMacroCommand(i18n("Insert Recorded MIDI"));

        command->addCommand(new EventQuantizeCommand
                            (*s,
                             s->getStartTime(),
                             s->getEndTime(),
                             "Notation Options",
                             true));

        command->addCommand(new NormalizeRestsCommand
                            (*s,
                             c.getBarStartForTime(s->getStartTime()),
                             c.getBarEndForTime(s->getEndTime())));

        command->addCommand(new SegmentRecordCommand(s));

        m_commandHistory->addCommand(command);
    }

    emit stoppedMIDIRecording();

    slotUpdateAllViews(0);
}

void
RosegardenGUIDoc::prepareAudio()
{
    if (!isSequencerRunning())
        return ;

    TQCString replyType;
    TQByteArray replyData;

    // Clear down the sequencer AudioFilePlayer object
    //
    rgapp->sequencerSend("clearAllAudioFiles()");

    for (AudioFileManagerIterator it = m_audioFileManager.begin();
            it != m_audioFileManager.end(); it++) {

        TQByteArray data;
        TQDataStream streamOut(data, IO_WriteOnly);

        // We have to pass the filename as a TQString
        //
        streamOut << TQString(strtoqstr((*it)->getFilename()));
        streamOut << (int)(*it)->getId();

        rgapp->sequencerCall("addAudioFile(TQString, int)", replyType, replyData, data);
        TQDataStream streamIn(replyData, IO_ReadOnly);
        int result;
        streamIn >> result;
        if (!result) {
            RG_DEBUG << "prepareAudio() - failed to add file \""
            << (*it)->getFilename() << "\"" << endl;
        }
    }
}

void
RosegardenGUIDoc::slotSetPointerPosition(timeT t)
{
    m_composition.setPosition(t);
    emit pointerPositionChanged(t);
}

void
RosegardenGUIDoc::setPlayPosition(timeT t)
{
    emit playPositionChanged(t);
}

void
RosegardenGUIDoc::setLoop(timeT t0, timeT t1)
{
    m_composition.setLoopStart(t0);
    m_composition.setLoopEnd(t1);
    emit loopChanged(t0, t1);
}

void
RosegardenGUIDoc::syncDevices()
{
    Profiler profiler("RosegardenGUIDoc::syncDevices", true);

    // Start up the sequencer
    //
    int timeout = 60;

    while (isSequencerRunning() && !rgapp->isSequencerRegistered() && timeout > 0) {
        RG_DEBUG << "RosegardenGUIDoc::syncDevices - "
        << "waiting for Sequencer to come up" << endl;

        ProgressDialog::processEvents();
        sleep(1); // 1s
        --timeout;
    }

    if (isSequencerRunning() && !rgapp->isSequencerRegistered() && timeout == 0) {

        // Give up, kill sequencer if possible, and report
        TDEProcess *proc = new TDEProcess;
        *proc << "/usr/bin/killall";
        *proc << "rosegardensequencer";
        *proc << "lt-rosegardensequencer";

        proc->start(TDEProcess::Block, TDEProcess::All);

        if (proc->exitStatus()) {
            RG_DEBUG << "couldn't kill any sequencer processes" << endl;
        }

        delete proc;
        RosegardenGUIApp *app = (RosegardenGUIApp*)parent();
        app->slotSequencerExited(0);
        return ;
    }

    if (!isSequencerRunning())
        return ;

    // Set the default timer first.  We only do this first time and
    // when changed in the configuration dialog.
    static bool setTimer = false;
    if (!setTimer) {
        kapp->config()->setGroup(SequencerOptionsConfigGroup);
        TQString currentTimer = getCurrentTimer();
        currentTimer = kapp->config()->readEntry("timer", currentTimer);
        setCurrentTimer(currentTimer);
        setTimer = true;
    }

    TQByteArray replyData;
    TQCString replyType;

    // Get number of devices the sequencer has found
    //
    rgapp->sequencerCall("getDevices()", replyType, replyData, RosegardenApplication::Empty, true);

    unsigned int devices = 0;

    if (replyType == "unsigned int") {
        TQDataStream reply(replyData, IO_ReadOnly);
        reply >> devices;
    } else {
        RG_DEBUG << "RosegardenGUIDoc::syncDevices - "
        << "got unknown returntype from getDevices()" << endl;
        return ;
    }

    RG_DEBUG << "RosegardenGUIDoc::syncDevices - devices = "
    << devices << endl;

    for (unsigned int i = 0; i < devices; i++) {

        RG_DEBUG << "RosegardenGUIDoc::syncDevices - i = "
        << i << endl;

        getMappedDevice(i);
    }

    RG_DEBUG << "RosegardenGUIDoc::syncDevices - "
    << "Sequencer alive - Instruments synced" << endl;


    // Force update of view on current track selection
    //
    kapp->config()->setGroup(GeneralOptionsConfigGroup);
    bool opt = kapp->config()->readBoolEntry("Show Track labels", true);
    TrackLabel::InstrumentTrackLabels labels = TrackLabel::ShowInstrument;
    if (opt)
        labels = TrackLabel::ShowTrack;

    RosegardenGUIView *w;
    for (w = m_viewList.first(); w != 0; w = m_viewList.next()) {
        w->slotSelectTrackSegments(m_composition.getSelectedTrack());
        w->getTrackEditor()->getTrackButtons()->changeTrackInstrumentLabels(labels);
    }

    emit devicesResyncd();
}

void
RosegardenGUIDoc::getMappedDevice(DeviceId id)
{
    TQByteArray data;
    TQByteArray replyData;
    TQCString replyType;
    TQDataStream arg(data, IO_WriteOnly);

    arg << (unsigned int)id;

    rgapp->sequencerCall("getMappedDevice(unsigned int)",
                         replyType, replyData, data);

    MappedDevice *mD = new MappedDevice();
    TQDataStream reply(replyData, IO_ReadOnly);

    if (replyType == "MappedDevice")
        // unfurl
        reply >> mD;
    else
        return ;

    // See if we've got this device already
    //
    Device *device = m_studio.getDevice(id);

    if (mD->getId() == Device::NO_DEVICE) {
        if (device)
            m_studio.removeDevice(id);
        delete mD;
        return ;
    }

    if (mD->size() == 0) {
        // no instruments is OK for a record device
        if (mD->getType() != Device::Midi ||
                mD->getDirection() != MidiDevice::Record) {

            RG_DEBUG << "RosegardenGUIDoc::getMappedDevice() - "
            << "no instruments found" << endl;
            if (device)
                m_studio.removeDevice(id);
            delete mD;
            return ;
        }
    }

    bool hadDeviceAlready = (device != 0);

    if (!hadDeviceAlready) {
        if (mD->getType() == Device::Midi) {
            device =
                new MidiDevice
                (id,
                 mD->getName(),
                 mD->getDirection());

            dynamic_cast<MidiDevice *>(device)
            ->setRecording(mD->isRecording());

            m_studio.addDevice(device);

            RG_DEBUG << "RosegardenGUIDoc::getMappedDevice - "
            << "adding MIDI Device \""
            << device->getName() << "\" id = " << id
            << " direction = " << mD->getDirection()
            << " recording = " << mD->isRecording()
            << endl;
        } else if (mD->getType() == Device::SoftSynth) {
            device = new SoftSynthDevice(id, mD->getName());
            m_studio.addDevice(device);

            RG_DEBUG << "RosegardenGUIDoc::getMappedDevice - "
            << "adding soft synth Device \""
            << device->getName() << "\" id = " << id << endl;
        } else if (mD->getType() == Device::Audio) {
            device = new AudioDevice(id, mD->getName());
            m_studio.addDevice(device);

            RG_DEBUG << "RosegardenGUIDoc::getMappedDevice - "
            << "adding audio Device \""
            << device->getName() << "\" id = " << id << endl;
        } else {
            RG_DEBUG << "RosegardenGUIDoc::getMappedDevice - "
            << "unknown device - \"" << mD->getName()
            << "\" (type = "
            << mD->getType() << ")\n";
            return ;
        }
    }

    if (hadDeviceAlready) {
        // direction might have changed
        if (mD->getType() == Device::Midi) {
            MidiDevice *midid =
                dynamic_cast<MidiDevice *>(device);
            if (midid) {
                midid->setDirection(mD->getDirection());
                midid->setRecording(mD->isRecording());
            }
        }
    }

    std::string connection(mD->getConnection());
    RG_DEBUG << "RosegardenGUIDoc::getMappedDevice - got \"" << connection
    << "\", direction " << mD->getDirection()
    << " recording " << mD->isRecording()
    << endl;
    device->setConnection(connection);

    Instrument *instrument;
    MappedDeviceIterator it;

    InstrumentList existingInstrs(device->getAllInstruments());

    for (it = mD->begin(); it != mD->end(); it++) {
        InstrumentId instrumentId = (*it)->getId();

        bool haveInstrument = false;
        for (InstrumentList::iterator iit = existingInstrs.begin();
                iit != existingInstrs.end(); ++iit) {

            if ((*iit)->getId() == instrumentId) {
                haveInstrument = true;
                break;
            }
        }

        if (!haveInstrument) {
            RG_DEBUG << "RosegardenGUIDoc::getMappedDevice: new instr " << (*it)->getId() << endl;
            instrument = new Instrument((*it)->getId(),
                                        (*it)->getType(),
                                        (*it)->getName(),
                                        (*it)->getChannel(),
                                        device);
            device->addInstrument(instrument);
        }
    }

    delete mD;
}

void
RosegardenGUIDoc::addRecordMIDISegment(TrackId tid)
{
    RG_DEBUG << "RosegardenGUIDoc::addRecordMIDISegment(" << tid << ")" << endl;
//    std::cerr << kdBacktrace() << std::endl;

    Segment *recordMIDISegment;

    recordMIDISegment = new Segment();
    recordMIDISegment->setTrack(tid);
    recordMIDISegment->setStartTime(m_recordStartTime);

    // Set an appropriate segment label
    //
    std::string label = "";

    Track *track = m_composition.getTrackById(tid);
    if (track) {
        if (track->getPresetLabel() != "") {
            label = track->getPresetLabel();
        } else if (track->getLabel() == "") {
            Instrument *instr =
                m_studio.getInstrumentById(track->getInstrument());
            if (instr) {
                label = m_studio.getSegmentName(instr->getId());
            }
        } else {
            label = track->getLabel();
        }
        label = qstrtostr(i18n("%1 (recorded)").arg(strtoqstr(label)));
    }

    recordMIDISegment->setLabel(label);

    Clef clef = clefIndexToClef(track->getClef());
    recordMIDISegment->insert(clef.getAsEvent
                              (recordMIDISegment->getStartTime()));

    // set segment transpose, color, highest/lowest playable from track parameters
    recordMIDISegment->setTranspose(track->getTranspose());
    recordMIDISegment->setColourIndex(track->getColor());
    recordMIDISegment->setHighestPlayable(track->getHighestPlayable());
    recordMIDISegment->setLowestPlayable(track->getLowestPlayable());

    m_composition.addSegment(recordMIDISegment);

    m_recordMIDISegments[track->getInstrument()] = recordMIDISegment;

    RosegardenGUIView *w;
    for (w = m_viewList.first(); w != 0; w = m_viewList.next()) {
        w->getTrackEditor()->getTrackButtons()->slotUpdateTracks();
    }

    emit newMIDIRecordingSegment(recordMIDISegment);
}

void
RosegardenGUIDoc::addRecordAudioSegment(InstrumentId iid,
                                        AudioFileId auid)
{
    Segment *recordSegment = new Segment
                             (Segment::Audio);

    // Find the right track

    Track *recordTrack = 0;

    const Composition::recordtrackcontainer &tr =
        getComposition().getRecordTracks();

    for (Composition::recordtrackcontainer::const_iterator i =
                tr.begin(); i != tr.end(); ++i) {
        TrackId tid = (*i);
        Track *track = getComposition().getTrackById(tid);
        if (track) {
            if (iid == track->getInstrument()) {
                recordTrack = track;
                break;
            }
        }
    }

    if (!recordTrack) {
        RG_DEBUG << "RosegardenGUIDoc::addRecordAudioSegment(" << iid << ", "
        << auid << "): No record-armed track found for instrument!"
        << endl;
        return ;
    }

    recordSegment->setTrack(recordTrack->getId());
    recordSegment->setStartTime(m_recordStartTime);
    recordSegment->setAudioStartTime(RealTime::zeroTime);

    // Set an appropriate segment label
    //
    std::string label = "";

    if (recordTrack) {
        if (recordTrack->getLabel() == "") {

            Instrument *instr =
                m_studio.getInstrumentById(recordTrack->getInstrument());

            if (instr) {
                label = instr->getName() + std::string(" ");
            }

        } else {
            label = recordTrack->getLabel() + std::string(" ");
        }

        label += std::string("(recorded audio)");
    }

    recordSegment->setLabel(label);
    recordSegment->setAudioFileId(auid);

    // set color for audio segment to distinguish it from a MIDI segment on an
    // audio track drawn with the pencil (depends on having the current
    // autoload.rg or a file derived from it to deliever predictable results,
    // but the worst case here is segments drawn in the wrong color when
    // adding new segments to old files, which I don't forsee as being enough
    // of a problem to be worth cooking up a more robust implementation of
    // this new color for new audio segments (DMM)
    recordSegment->setColourIndex(GUIPalette::AudioDefaultIndex);

    RG_DEBUG << "RosegardenGUIDoc::addRecordAudioSegment: adding record segment for instrument " << iid << " on track " << recordTrack->getId() << endl;
    m_recordAudioSegments[iid] = recordSegment;

    RosegardenGUIView *w;
    for (w = m_viewList.first(); w != 0; w = m_viewList.next()) {
        w->getTrackEditor()->getTrackButtons()->slotUpdateTracks();
    }

    emit newAudioRecordingSegment(recordSegment);
}

void
RosegardenGUIDoc::updateRecordingAudioSegments()
{
    const Composition::recordtrackcontainer &tr =
        getComposition().getRecordTracks();

    for (Composition::recordtrackcontainer::const_iterator i =
                tr.begin(); i != tr.end(); ++i) {

        TrackId tid = (*i);
        Track *track = getComposition().getTrackById(tid);

        if (track) {

            InstrumentId iid = track->getInstrument();

            if (m_recordAudioSegments[iid]) {

                Segment *recordSegment = m_recordAudioSegments[iid];
                if (!recordSegment->getComposition()) {

                    // always insert straight away for audio
                    m_composition.addSegment(recordSegment);
                }

                recordSegment->setAudioEndTime(
                    m_composition.getRealTimeDifference(recordSegment->getStartTime(),
                                                        m_composition.getPosition()));

            } else {
                // 		RG_DEBUG << "RosegardenGUIDoc::updateRecordingAudioSegments: no segment for instr "
                // 			 << iid << endl;
            }
        }
    }
}

void
RosegardenGUIDoc::stopRecordingAudio()
{
    RG_DEBUG << "RosegardenGUIDoc::stopRecordingAudio" << endl;

    for (RecordingSegmentMap::iterator ri = m_recordAudioSegments.begin();
            ri != m_recordAudioSegments.end(); ++ri) {

        Segment *recordSegment = ri->second;

        if (!recordSegment)
            continue;

        // set the audio end time
        //
        recordSegment->setAudioEndTime(
            m_composition.getRealTimeDifference(recordSegment->getStartTime(),
                                                m_composition.getPosition()));

        // now add the Segment
        RG_DEBUG << "RosegardenGUIDoc::stopRecordingAudio - "
        << "got recorded segment" << endl;

        // now move the segment back by the record latency
        //
        /*!!!
          No.  I don't like this.
         
          The record latency doesn't always exist -- for example, if recording
          from a synth plugin there is no record latency, and we have no way
          here to distinguish.
         
          The record latency is a total latency figure that actually includes
          some play latency, and we compensate for that again on playback (see
          bug #1378766).
         
          The timeT conversion of record latency is approximate in frames,
          giving potential phase error.
         
          Cutting this out won't break any existing files, as the latency
          compensation there is already encoded into the file.
         
        	RealTime adjustedStartTime =
        	    m_composition.getElapsedRealTime(recordSegment->getStartTime()) -
        	    m_audioRecordLatency;
         
        	timeT shiftedStartTime =
        	    m_composition.getElapsedTimeForRealTime(adjustedStartTime);
         
        	RG_DEBUG << "RosegardenGUIDoc::stopRecordingAudio - "
                         << "shifted recorded audio segment by "
                         <<  recordSegment->getStartTime() - shiftedStartTime
        		 << " clicks (from " << recordSegment->getStartTime()
        		 << " to " << shiftedStartTime << ")" << endl;
         
        	recordSegment->setStartTime(shiftedStartTime);
        */
    }
    emit stoppedAudioRecording();
}

void
RosegardenGUIDoc::finalizeAudioFile(InstrumentId iid)
{
    RG_DEBUG << "RosegardenGUIDoc::finalizeAudioFile(" << iid << ")" << endl;

    Segment *recordSegment = 0;
    recordSegment = m_recordAudioSegments[iid];

    if (!recordSegment) {
        RG_DEBUG << "RosegardenGUIDoc::finalizeAudioFile: Failed to find segment" << endl;
        return ;
    }

    AudioFile *newAudioFile = m_audioFileManager.getAudioFile
                              (recordSegment->getAudioFileId());
    if (!newAudioFile) {
        std::cerr << "WARNING: RosegardenGUIDoc::finalizeAudioFile: No audio file found for instrument " << iid << " (audio file id " << recordSegment->getAudioFileId() << ")" << std::endl;
        return ;
    }

    // Create a progress dialog
    //
    ProgressDialog *progressDlg = new ProgressDialog
                                  (i18n("Generating audio preview..."), 100, (TQWidget*)parent());
    progressDlg->setAutoClose(false);
    progressDlg->setAutoReset(false);
    progressDlg->show();

    connect(progressDlg, TQ_SIGNAL(cancelClicked()),
            &m_audioFileManager, TQ_SLOT(slotStopPreview()));

    connect(&m_audioFileManager, TQ_SIGNAL(setProgress(int)),
            progressDlg->progressBar(), TQ_SLOT(setValue(int)));

    try {
        m_audioFileManager.generatePreview(newAudioFile->getId());
        //!!! mtr just for now?: or better to do this once after the fact?
        //!!!	m_audioFileManager.generatePreviews();
    } catch (Exception e) {
        TDEStartupLogo::hideIfStillThere();
        CurrentProgressDialog::freeze();
        KMessageBox::error(0, strtoqstr(e.getMessage()));
        CurrentProgressDialog::thaw();
    }

    delete progressDlg;

    if (!recordSegment->getComposition()) {
        getComposition().addSegment(recordSegment);
    }

    m_commandHistory->addCommand
    (new SegmentRecordCommand(recordSegment));

    // update views
    slotUpdateAllViews(0);

    // Now install the file in the sequencer
    //
    // We're playing fast and loose with DCOP here - we just send
    // this request and carry on regardless otherwise the sequencer
    // can just hang our request.  We don't risk a call() and we
    // don't get a return type.  Ugly and hacky but it appears to
    // work for me - so hey.
    //
    TQByteArray data;
    TQDataStream streamOut(data, IO_WriteOnly);
    streamOut << TQString(strtoqstr(newAudioFile->getFilename()));
    streamOut << (int)newAudioFile->getId();
    rgapp->sequencerSend("addAudioFile(TQString, int)", data);

    // clear down
    m_recordAudioSegments.erase(iid);
    emit audioFileFinalized(recordSegment);
}

RealTime
RosegardenGUIDoc::getAudioPlayLatency()
{
    TQCString replyType;
    TQByteArray replyData;

    if (!rgapp->sequencerCall("getAudioPlayLatency()", replyType, replyData)) {
        RG_DEBUG << "RosegardenGUIDoc::getAudioPlayLatency - "
        << "Playback failed to contact Rosegarden sequencer"
        << endl;
        return RealTime::zeroTime;
    }

    // ensure the return type is ok
    TQDataStream streamIn(replyData, IO_ReadOnly);
    MappedRealTime result;
    streamIn >> result;

    return (result.getRealTime());
}

RealTime
RosegardenGUIDoc::getAudioRecordLatency()
{
    TQCString replyType;
    TQByteArray replyData;

    if (!rgapp->sequencerCall("getAudioRecordLatency()", replyType, replyData)) {
        RG_DEBUG << "RosegardenGUIDoc::getAudioRecordLatency - "
        << "Playback failed to contact Rosegarden sequencer"
        << endl;
        return RealTime::zeroTime;
    }

    // ensure the return type is ok
    TQDataStream streamIn(replyData, IO_ReadOnly);
    MappedRealTime result;
    streamIn >> result;

    return (result.getRealTime());
}

void
RosegardenGUIDoc::updateAudioRecordLatency()
{
    m_audioRecordLatency = getAudioRecordLatency();
}

TQStringList
RosegardenGUIDoc::getTimers()
{
    TQStringList list;

    TQCString replyType;
    TQByteArray replyData;

    if (!rgapp->sequencerCall("getTimers()", replyType, replyData)) {
        RG_DEBUG << "RosegardenGUIDoc::getTimers - "
        << "failed to contact Rosegarden sequencer" << endl;
        return list;
    }

    if (replyType != "unsigned int") {
        RG_DEBUG << "RosegardenGUIDoc::getTimers - "
        << "wrong reply type (" << replyType << ") from sequencer" << endl;
        return list;
    }

    TQDataStream streamIn(replyData, IO_ReadOnly);
    unsigned int count = 0;
    streamIn >> count;

    for (unsigned int i = 0; i < count; ++i) {

        TQByteArray data;
        TQDataStream streamOut(data, IO_WriteOnly);

        streamOut << i;

        if (!rgapp->sequencerCall("getTimer(unsigned int)",
                                  replyType, replyData, data)) {
            RG_DEBUG << "RosegardenGUIDoc::getTimers - "
            << "failed to contact Rosegarden sequencer" << endl;
            return list;
        }

        if (replyType != "TQString") {
            RG_DEBUG << "RosegardenGUIDoc::getTimers - "
            << "wrong reply type (" << replyType << ") from sequencer" << endl;
            return list;
        }

        TQDataStream streamIn(replyData, IO_ReadOnly);
        TQString name;
        streamIn >> name;

        list.push_back(name);
    }

    return list;
}

TQString
RosegardenGUIDoc::getCurrentTimer()
{
    TQCString replyType;
    TQByteArray replyData;

    if (!rgapp->sequencerCall("getCurrentTimer()", replyType, replyData)) {
        RG_DEBUG << "RosegardenGUIDoc::getCurrentTimer - "
        << "failed to contact Rosegarden sequencer" << endl;
        return "";
    }

    if (replyType != "TQString") {
        RG_DEBUG << "RosegardenGUIDoc::getCurrentTimer - "
        << "wrong reply type (" << replyType << ") from sequencer" << endl;
        return "";
    }

    TQDataStream streamIn(replyData, IO_ReadOnly);
    TQString name;
    streamIn >> name;
    return name;
}

void
RosegardenGUIDoc::setCurrentTimer(TQString name)
{
    TQCString replyType;
    TQByteArray replyData;

    TQByteArray data;
    TQDataStream streamOut(data, IO_WriteOnly);

    streamOut << name;

    if (!rgapp->sequencerCall("setCurrentTimer(TQString)",
                              replyType, replyData, data)) {
        RG_DEBUG << "RosegardenGUIDoc::setCurrentTimer - "
        << "failed to contact Rosegarden sequencer" << endl;
    }
}

void
RosegardenGUIDoc::initialiseControllers()
{
    InstrumentList list = m_studio.getAllInstruments();
    MappedComposition mC;
    MappedEvent *mE;

    InstrumentList::iterator it = list.begin();
    for (; it != list.end(); it++) {
        if ((*it)->getType() == Instrument::Midi) {
            std::vector<MidiControlPair> advancedControls;

            // push all the advanced static controls
            //
            StaticControllers &list = (*it)->getStaticControllers();
            for (StaticControllerConstIterator cIt = list.begin(); cIt != list.end(); ++cIt) {
                advancedControls.push_back(MidiControlPair(cIt->first, cIt->second));
            }

            advancedControls.
            push_back(
                MidiControlPair(MIDI_CONTROLLER_PAN,
                                (*it)->getPan()));
            advancedControls.
            push_back(
                MidiControlPair(MIDI_CONTROLLER_VOLUME,
                                (*it)->getVolume()));


            std::vector<MidiControlPair>::iterator
            iit = advancedControls.begin();
            for (; iit != advancedControls.end(); iit++) {
                try {
                    mE =
                        new MappedEvent((*it)->getId(),
                                        MappedEvent::MidiController,
                                        iit->first,
                                        iit->second);
                } catch (...) {
                    continue;
                }

                mC.insert(mE);
            }
        }
    }

    StudioControl::sendMappedComposition(mC);
}

void
RosegardenGUIDoc::clearAllPlugins()
{
    //RG_DEBUG << "clearAllPlugins" << endl;

    InstrumentList list = m_studio.getAllInstruments();
    MappedComposition mC;

    InstrumentList::iterator it = list.begin();
    for (; it != list.end(); it++) {
        if ((*it)->getType() == Instrument::Audio) {
            PluginInstanceIterator pIt = (*it)->beginPlugins();

            for (; pIt != (*it)->endPlugins(); pIt++) {
                if ((*pIt)->getMappedId() != -1) {
                    if (StudioControl::
                            destroyStudioObject((*pIt)->getMappedId()) == false) {
                        RG_DEBUG << "RosegardenGUIDoc::clearAllPlugins - "
                        << "couldn't find plugin instance "
                        << (*pIt)->getMappedId() << endl;
                    }
                }
                (*pIt)->clearPorts();
            }
            (*it)->emptyPlugins();

            /*
            RG_DEBUG << "RosegardenGUIDoc::clearAllPlugins - "
                     << "cleared " << (*it)->getName() << endl;
            */
        }
    }
}

Clipboard*
RosegardenGUIDoc::getClipboard()
{
    RosegardenGUIApp *app = (RosegardenGUIApp*)parent();
    return app->getClipboard();
}

void RosegardenGUIDoc::slotDocColoursChanged()
{
    RG_DEBUG << "RosegardenGUIDoc::slotDocColoursChanged(): emitting docColoursChanged()" << endl;

    emit docColoursChanged();
}

void
RosegardenGUIDoc::addOrphanedRecordedAudioFile(TQString fileName)
{
    m_orphanedRecordedAudioFiles.push_back(fileName);
    slotDocumentModified();
}

void
RosegardenGUIDoc::addOrphanedDerivedAudioFile(TQString fileName)
{
    m_orphanedDerivedAudioFiles.push_back(fileName);
    slotDocumentModified();
}

void
RosegardenGUIDoc::notifyAudioFileRemoval(AudioFileId id)
{
    AudioFile *file = 0;

    if (m_audioFileManager.wasAudioFileRecentlyRecorded(id)) {
        file = m_audioFileManager.getAudioFile(id);
        if (file) addOrphanedRecordedAudioFile(file->getFilename().c_str());
        return;
    }

    if (m_audioFileManager.wasAudioFileRecentlyDerived(id)) {
        file = m_audioFileManager.getAudioFile(id);
        if (file) addOrphanedDerivedAudioFile(file->getFilename().c_str());
        return;
    }
}

}
#include "RosegardenGUIDoc.moc"
