/*
 *
 *    soniK digital audio editor
 *    Copyright (C) 2003-2006  Robert Walker <rob@tenfoot.org.uk>
 *
 *    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.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with this program; if not, write to the Free Software
 *    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */
#include "part.h"
#include "partwidget.h"
#include "audiodragobject.h"

#include "multiplugin.h"
#include "fileio.h"
#include "audioio.h"
#include "display.h"

#include "saveoptsdlg.h"
#include "configdialog.h"
#include "recorddialog.h"
#include "formatdialog.h"

#include "data.h"
#include "fileio.h"
#include "audioio.h"
#include "edit.h"

#include "partsettings.h"

#include "controls/repeataction.h"

#include <kaboutdata.h>
#include <kaction.h>
#include <kstdaction.h>
#include <klocale.h>
#include <kfiledialog.h>
#include <kmimemagic.h>
#include <kmimetype.h>
#include <kparts/plugin.h>
#include <kapplication.h>
#include <kmessagebox.h>
#include <kinstance.h>
#include <kparts/genericfactory.h>
#include <kwin.h>
#include <kxmlguifactory.h>
#include <kio/netaccess.h>

#include <kdebug.h>

#include <qfile.h>
#include <qfileinfo.h>
#include <qtextstream.h>
#include <qmultilineedit.h>
#include <qapplication.h>
#include <qclipboard.h>
#include <qpopupmenu.h>

using Sonik::Data;
using Sonik::Part;
using Sonik::PartWidget;
using Sonik::AudioIOManager;

using Sonik::MultiPlugin;
using Sonik::FileIO;
using Sonik::AudioIO;
using Sonik::Edit;

using Sonik::SaveOptionsDialog;
using Sonik::ValueScale;

namespace Sonik
{
  typedef KParts::GenericFactory<Part> SonikPartFactory;
}

K_EXPORT_COMPONENT_FACTORY(libsonikpart, Sonik::SonikPartFactory);

namespace
{
  void connectData(Data *sender, QObject *receiver)
  {
    QObject::connect(sender,   SIGNAL(channelsChanged(uint8_t)),
                     receiver, SLOT(dataChannelsChanged(uint8_t)));
    QObject::connect(sender,   SIGNAL(lengthChanged(size_t)),
                     receiver, SLOT(dataLengthChanged(size_t)));
    QObject::connect(sender,   SIGNAL(sampleRateChanged(uint32_t)),
                     receiver, SLOT(dataSampleRateChanged(uint32_t)));
    QObject::connect(sender,   SIGNAL(bitsChanged(uint8_t)),
                     receiver, SLOT(dataBitsChanged(uint8_t)));
    QObject::connect(sender,   SIGNAL(dataChanged(uint8_t, off_t, size_t)),
                     receiver, SLOT(dataDataChanged(uint8_t, off_t, size_t)));
  }

  void connectWidget(PartWidget *sender, QObject *receiver)
  {
    QObject::connect(sender,   SIGNAL(selectionChanged(off_t, size_t)),
                     receiver, SLOT(viewSelectionChanged(off_t, size_t)));
    QObject::connect(sender,   SIGNAL(displaySelectionChanged(off_t, size_t)),
                     receiver, SLOT(viewDisplaySelectionChanged(off_t, size_t)));
    QObject::connect(sender,   SIGNAL(cursorPosChanged(off_t)),
                     receiver, SLOT(viewCursorPosChanged(off_t)));
    QObject::connect(sender,   SIGNAL(playbackPosChanged(off_t)),
                     receiver, SLOT(viewPlaybackPosChanged(off_t)));
  }

  void connectAudioIO(AudioIOManager *sender, QObject *receiver)
  {
    QObject::connect(sender,   SIGNAL(playing()),
                     receiver, SLOT(audioIOPlaying()));
    QObject::connect(sender,   SIGNAL(recording()),
                     receiver, SLOT(audioIORecording()));
    QObject::connect(sender,   SIGNAL(paused()),
                     receiver, SLOT(audioIOPaused()));
    QObject::connect(sender,   SIGNAL(stopped()),
                     receiver, SLOT(audioIOStopped()));
    QObject::connect(sender,   SIGNAL(position(off_t)),
                     receiver, SLOT(audioIOPosition(off_t)));
  }

  Sonik::Plugin* findPlugin(const Sonik::PluginList& plugins,
                            const QString& name)
  {
    Sonik::PluginList::const_iterator it = plugins.begin(), e = plugins.end();
    for ( ; it != e; ++it)
      if ((*it)->pluginName() == name)
        return *it;

    return 0;
  }

  const uint kSkipSize = 20;
}

uint Part::sNextId = 0;
bool Part::sNoAudioPluginsConfirmed = false;

Part::Part(QWidget* parentWidget, const char* /*widgetName*/,
           QObject* parent, const char* name, const QStringList& /*args*/)
  : DCOPObject(QString("SonikPart-%1").arg(sNextId).latin1()),
    KParts::ReadWritePart(parent, name),
    mData(mActionManager),
    mId(sNextId++),
    mOptionsDialogNeeded(true),
    mAudioIOManager(mData),
    mActiveAudioIOPlugin(0),
    mRecordDialog(0),
    mConfigDlg(0),
    mRecordStart(-1),
    mLastPos(-1)
{
  setInstance(Sonik::SonikPartFactory::instance());

  //
  // Create part widget
  //
  PartWidget* widget = new PartWidget(*this, mActionManager, parentWidget, "sonikpartwidget");
  setWidget(widget);
  widget->select(0, 0);

  //
  // Forward signals to external observers
  //

  // data
  connect(&mData, SIGNAL(channelsChanged(uint8_t)),
          this,   SIGNAL(channelsChanged(uint8_t)));
  connect(&mData, SIGNAL(lengthChanged(size_t)),
          this,   SIGNAL(lengthChanged(size_t)));
  connect(&mData, SIGNAL(sampleRateChanged(uint32_t)),
          this,   SIGNAL(sampleRateChanged(uint32_t)));
  connect(&mData, SIGNAL(bitsChanged(uint8_t)),
          this,   SIGNAL(bitsChanged(uint8_t)));
  connect(&mData, SIGNAL(dataChanged(uint8_t, off_t, size_t)),
          this,   SIGNAL(dataChanged(uint8_t, off_t, size_t)));

  connectData(&mData, this);

  // view
  connect(widget, SIGNAL(selectionChanged(off_t, size_t)),
          this,   SIGNAL(selectionChanged(off_t, size_t)));
  connect(widget, SIGNAL(displaySelectionChanged(off_t, size_t)),
          this,   SIGNAL(displaySelectionChanged(off_t, size_t)));
  connect(widget, SIGNAL(cursorPosChanged(off_t)),
          this,   SIGNAL(cursorPosChanged(off_t)));
  connect(widget, SIGNAL(playbackPosChanged(off_t)),
          this,   SIGNAL(playbackPosChanged(off_t)));

  connectWidget(widget, this);

  // audio io
  connect(&mAudioIOManager, SIGNAL(playing()),
          this,             SIGNAL(playing()));
  connect(&mAudioIOManager, SIGNAL(recording()),
          this,             SIGNAL(recording()));
  connect(&mAudioIOManager, SIGNAL(paused()),
          this,             SIGNAL(paused()));
  connect(&mAudioIOManager, SIGNAL(stopped()),
          this,             SIGNAL(stopped()));
  connect(&mAudioIOManager, SIGNAL(position(off_t)),
          this,             SIGNAL(position(off_t)));

  connectAudioIO(&mAudioIOManager, this);

  // action manager
  connect(&mActionManager, SIGNAL(undoChanged(const QString&)),
          this,            SLOT(actionManagerUndoChanged(const QString&)));
  connect(&mActionManager, SIGNAL(redoChanged(const QString&)),
          this,            SLOT(actionManagerRedoChanged(const QString&)));

  // connect widget signals
  connectData(&mData, widget);
  connectAudioIO(&mAudioIOManager, widget);

  setupActions();

  setupPlugins();

  // set XML-UI resource file
  setXMLFile("sonikpartui.rc");

  setReadWrite(true);

  setTimeFormat(
    static_cast<Sonik::TimeFormat>(PartSettings::defaultTimeFormat())
    );
  setValueFormat(
    static_cast<Sonik::ValueFormat>(PartSettings::defaultValueScaleType())
    );

  mData.resumeSignals(); // broadcast initial state

  actionManagerUndoChanged(QString::null);
  actionManagerRedoChanged(QString::null);
  mActionManager.setEnabled(true);

  initialized();
}

Part::~Part()
{
}

KAboutData* Part::createAboutData()
{
  KAboutData *aboutData = new KAboutData("sonikpart", I18N_NOOP("soniK Part"), "0.1");
  aboutData->addAuthor("Rob Walker", 0, "rob@tenfoot.org.uk");
  return aboutData;
}

PartWidget* Part::partWidget()
{
  return static_cast<PartWidget *>(widget());
}

const PartWidget* Part::partWidget() const
{
  return static_cast<const PartWidget *>(
    const_cast<Part *>(this)->widget()
    );
}

void Part::setupActions()
{
  // File menu
  mFileSave = KStdAction::save(this, SLOT(save()), actionCollection());
  KStdAction::saveAs(this, SLOT(uiFileSaveAs()), actionCollection());

  // Edit menu
  mEditUndo  = KStdAction::undo(this, SLOT(uiEditUndo()), actionCollection());
  mEditRedo  = KStdAction::redo(this, SLOT(uiEditRedo()), actionCollection());
  mEditCut   = KStdAction::cut(this, SLOT(uiEditCut()), actionCollection());
  mEditCut->setToolTip(
    i18n("Cuts the selected section and puts it to the clipboard")
    );
  mEditCopy  = KStdAction::copy(this, SLOT(uiEditCopy()), actionCollection());
  mEditCopy->setToolTip(
    i18n("Copies the selected section to the clipboard")
    );
  mEditPaste = KStdAction::paste(this, SLOT(uiEditPaste()), actionCollection());
  mEditPaste->setToolTip(
    i18n("Pastes the clipboard contents to actual position")
    );
  mEditDelete       = new KAction(i18n("&Delete"),
                                  "editdelete",
                                  Key_Delete,
                                  this, SLOT(uiEditDelete()),
                                  actionCollection(), "edit_delete");
  mEditCrop         = new KAction(i18n("&Crop"),
                                  SHIFT+Key_Delete,
                                  this, SLOT(uiEditCrop()),
                                  actionCollection(), "edit_crop");
  mEditSelectAll    = new KAction(i18n("&Select All"),
                                  CTRL+Key_A,
                                  this, SLOT(uiEditSelectAll()),
                                  actionCollection(), "edit_select_all");
  mEditSelectNone   = new KAction(i18n("&Select None"),
                                  SHIFT+CTRL+Key_A,
                                  this, SLOT(uiEditSelectNone()),
                                  actionCollection(), "edit_select_none");
  //   mEditSelectInvert = new KAction(i18n("&Invert Selection"),
  //                                   Key_I,
  //                                   this, SLOT(uiEditSelectInvert()),
  //                                   actionCollection(), "edit_select_invert");

  // View menu
  mViewZoomIn  = KStdAction::zoomIn(this, SLOT(uiViewZoomIn()), actionCollection());
  mViewZoomOut = KStdAction::zoomOut(this, SLOT(uiViewZoomOut()), actionCollection());
  mViewTimeFormatSamples    = new KRadioAction(i18n("Samples"), 0, this,
                                               SLOT(uiViewTimeFormatChanged()),
                                               actionCollection(),
                                               "time_format_samples");
  mViewTimeFormatTime       = new KRadioAction(i18n("Time"), 0, this,
                                               SLOT(uiViewTimeFormatChanged()),
                                               actionCollection(),
                                               "time_format_time");
  mViewTimeFormatMillisec   = new KRadioAction(i18n("Milliseconds"), 0, this,
                                               SLOT(uiViewTimeFormatChanged()),
                                               actionCollection(),
                                               "time_format_millisec");
  mViewTimeFormatCDDAFrames = new KRadioAction(i18n("CDDA Frames"), 0, this,
                                               SLOT(uiViewTimeFormatChanged()),
                                               actionCollection(),
                                               "time_format_cdda_frames");
  mViewTimeFormatPALFrames  = new KRadioAction(i18n("PAL Frames"), 0, this,
                                               SLOT(uiViewTimeFormatChanged()),
                                               actionCollection(),
                                               "time_format_pal_frames");
  mViewTimeFormatNTSCFrames = new KRadioAction(i18n("NTSC Frames"), 0, this,
                                               SLOT(uiViewTimeFormatChanged()),
                                               actionCollection(),
                                               "time_format_ntsc_frames");

  mViewTimeFormatSamples->setExclusiveGroup("timeformat");
  mViewTimeFormatTime->setExclusiveGroup("timeformat");
  mViewTimeFormatMillisec->setExclusiveGroup("timeformat");
  mViewTimeFormatCDDAFrames->setExclusiveGroup("timeformat");
  mViewTimeFormatPALFrames->setExclusiveGroup("timeformat");
  mViewTimeFormatNTSCFrames->setExclusiveGroup("timeformat");

  mViewValueFormatNormalised = new KRadioAction(i18n("Normalised"), 0, this,
                                                SLOT(uiViewValueFormatChanged()),
                                                actionCollection(),
                                                "value_format_norm");
  mViewValueFormatReal       = new KRadioAction(i18n("Real"), 0, this,
                                                SLOT(uiViewValueFormatChanged()),
                                                actionCollection(),
                                                "value_format_real");
  mViewValueFormatDb         = new KRadioAction(i18n("dB"), 0, this,
                                                SLOT(uiViewValueFormatChanged()),
                                                actionCollection(),
                                                "value_format_db");

  mViewValueFormatNormalised->setExclusiveGroup("valueformat");
  mViewValueFormatReal->setExclusiveGroup("valueformat");
  mViewValueFormatDb->setExclusiveGroup("valueformat");

  // Play menu
  mPlayStart = new KAction(i18n("&Start"), "player_start",
                           KShortcut::null(),
                           this, SLOT(uiPlayStart()),
                           actionCollection(), "play_start");
  mPlayBack = new RepeatAction(i18n("&Back"), "player_rew",
                               KShortcut::null(),
                               this, SLOT(uiPlayBack()),
                               actionCollection(), "play_back");
  mPlayForward = new RepeatAction(i18n("&Forward"), "player_fwd",
                                  KShortcut::null(),
                                  this, SLOT(uiPlayForward()),
                                  actionCollection(), "play_forward");
  mPlayEnd = new KAction(i18n("&End"), "player_end",
                         KShortcut::null(),
                         this, SLOT(uiPlayEnd()),
                         actionCollection(), "play_end");
  mPlayPlay = new KAction(i18n("&Play"), "player_play",
                          CTRL+Key_Enter,
                          this, SLOT(uiPlayPlay()),
                          actionCollection(), "play_play");
  mPlayPlayLooped = new KAction(i18n("&Play Looped"), "sonik_playloop",
                                CTRL+SHIFT+Key_Enter,
                                this, SLOT(uiPlayPlayLooped()),
                                actionCollection(), "play_playloop");
  mPlayPause = new KAction(i18n("&Pause"), "player_pause",
                           CTRL+Key_P,
                           this, SLOT(uiPlayPause()),
                           actionCollection(), "play_pause");
  mPlayStop = new KAction(i18n("&Stop"), "player_stop",
                          CTRL+Key_S,
                          this, SLOT(uiPlayStop()),
                          actionCollection(), "play_stop");
  mPlayRecord = new KAction(i18n("&Record"), "sonik_record",
                            CTRL+Key_R,
                            this, SLOT(uiPlayRecord()),
                            actionCollection(), "play_record");

  // Settings menu
  KStdAction::preferences(this, SLOT(uiSettingsConfigure()),
                          actionCollection());
}

void Part::setupPlugins()
{
  // load plugins as a queue - a multi plugin will add to the back of the
  // queue
  // TODO: scan .desktop files manually - allow for disabling
  QPtrList<KParts::Plugin> pluginQueue = KParts::Plugin::pluginObjects(this);
  KParts::Plugin* plugin = pluginQueue.getFirst();
  while (plugin != 0)
  {
    if (plugin->inherits("Sonik::MultiPlugin"))
    {
      MultiPlugin* multiPlugin = static_cast<MultiPlugin*>(plugin);
      kdDebug(60606) << "Sonik::MultiPlugin: "
                     << multiPlugin->pluginName() << "\n";
      MultiPlugin::PluginList pluginList = multiPlugin->plugins();
      KParts::Plugin* p;
      for (p = pluginList.first(); p != 0; p = pluginList.next())
        pluginQueue.append(p);
    }
    else if (plugin->inherits("Sonik::FileIO"))
    {
      FileIO* f = static_cast<FileIO*>(plugin);
      kdDebug(60606) << "Sonik::FileIO: " << f->pluginName() << "\n";
      mFileIOPlugins.append(f);
      mFileIOFactory.add(f);
    }
    else if (plugin->inherits("Sonik::AudioIO"))
    {
      AudioIO* a = static_cast<AudioIO*>(plugin);
      kdDebug(60606) << "Sonik::AudioIO: " << a->pluginName() << "\n";
      mAudioIOPlugins.append(a);
    }
    else if (plugin->inherits("Sonik::Display"))
    {
      Sonik::Display* d = static_cast<Sonik::Display*>(plugin);
      kdDebug(60606) << "Sonik::Display: " << d->pluginName() << "\n";
      mDisplayPlugins.append(d);

      connect(d, SIGNAL(pluginSelected(Sonik::Display*)),
              this, SLOT(selectDisplayPlugin(Sonik::Display*)));
    }
    else if (plugin->inherits("Sonik::Edit"))
    {
      Edit* e = static_cast<Edit*>(plugin);
      kdDebug(60606) << "Sonik::Edit: " << e->pluginName() << "\n";
      e->init(&mData, partWidget(), &mActionManager);
      mEditPlugins.append(e);
    }
    else
    {
      kdDebug(60606) << "Unknown plugin: " << plugin->className()
                     << " in " << plugin->xmlFile() << "\n";
      kdDebug(60606) << "Superclass is : "
                     << plugin->metaObject()->superClassName()
                     << " in " << plugin->xmlFile() << "\n";
    }

    pluginQueue.removeFirst();
    plugin = pluginQueue.getFirst();
  }

  kdDebug(60606) << "Part::setupPlugins: disp " << mDisplayPlugins.count()
                 << ", file " << mFileIOPlugins.count()
                 << ", audio " << mAudioIOPlugins.count()
                 << "\n";

  // exit if no display plugins found
  if (mDisplayPlugins.empty())
  {
    KMessageBox::error(0, i18n("No display plugins found. Please check that soniK was installed correctly"));
    exit(-1);
  }

  // give user option to exit if no audio io plugins found
  if (mAudioIOPlugins.empty() && !sNoAudioPluginsConfirmed)
  {
    if (KMessageBox::warningContinueCancel(0, i18n("No audioIO plugins found. You will not be able to play or record sounds")) != KMessageBox::Continue)
      exit(-1);
    sNoAudioPluginsConfirmed = true;
  }

  //
  // select initial plugins
  //

  // display - try configured, normal, then first
  Plugin* p;
  p = findPlugin(mDisplayPlugins, PartSettings::defaultDisplay());
  if (p == 0)
    p = findPlugin(mDisplayPlugins, "normal");
  if (p == 0)
    p = mDisplayPlugins.first();
  selectDisplayPlugin(static_cast<Sonik::Display*>(p));

  p = findPlugin(mAudioIOPlugins, "arts");
  if (p == 0 && !mAudioIOPlugins.empty())
    // fall back to first (if any)
    p = mAudioIOPlugins.first();
  selectAudioIOPlugin(static_cast<AudioIO*>(p));

  kdDebug(60606) << "Part::setupPlugins: " << "exit" << "\n";
}

Sonik::PluginList Part::fileIOPlugins() const
{
  return mFileIOPlugins;
}

Sonik::PluginList Part::audioIOPlugins() const
{
  return mAudioIOPlugins;
}

Sonik::PluginList Part::displayPlugins() const
{
  return mDisplayPlugins;
}

Sonik::PluginList Part::editPlugins() const
{
  return mEditPlugins;
}

void Part::setDefaultDisplay(const QString& name)
{
  if (name == PartSettings::defaultDisplay())
    return;

  Plugin* p = findPlugin(mDisplayPlugins, name);
  if (p != 0)
  {
    PartSettings::setDefaultDisplay(name);
    selectDisplayPlugin(static_cast<Sonik::Display*>(p));
  }
}

Sonik::TimeFormat Part::timeFormat() const
{
  return partWidget()->timeFormat();
}

void Part::setTimeFormat(Sonik::TimeFormat fmt)
{
  switch (fmt)
  {
    case kSamples:
      mViewTimeFormatSamples->setChecked(true);
      break;

    case kH_M_S_Ms:
      mViewTimeFormatTime->setChecked(true);
      break;

    case kMs:
      mViewTimeFormatMillisec->setChecked(true);
      break;

    case kH_M_S_FCDDA:
      mViewTimeFormatCDDAFrames->setChecked(true);
      break;

    case kH_M_S_FPAL:
      mViewTimeFormatPALFrames->setChecked(true);
      break;

    case kH_M_S_FNTSC:
      mViewTimeFormatNTSCFrames->setChecked(true);
      break;

    default:
      mViewTimeFormatSamples->setChecked(true);
      fmt = kSamples;
      break;
  }
  partWidget()->setTimeFormat(fmt);

  emit timeFormatChanged(fmt);
}

Sonik::ValueFormat Part::valueFormat() const
{
  return partWidget()->valueFormat();
}

void Part::setValueFormat(ValueFormat fmt)
{
  switch (fmt)
  {
    case kNormalised:
      mViewValueFormatNormalised->setChecked(true);
      break;

    case kValue:
      mViewValueFormatReal->setChecked(true);
      break;

    case kValueDB:
      mViewValueFormatDb->setChecked(true);
      break;

    default:
      mViewValueFormatNormalised->setChecked(true);
      fmt = kNormalised;
      break;
  }

  // widget checks if format is valid for display
  partWidget()->setValueFormat(fmt);

  emit valueFormatChanged(partWidget()->valueFormat());
}

void Part::selectAudioIOPlugin(AudioIO* p)
{
  mActiveAudioIOPlugin = p;

  bool pluginValid = (p != 0);

  mPlayPlay->setEnabled(pluginValid);
  mPlayPlayLooped->setEnabled(pluginValid);
  mPlayPause->setEnabled(false);
  mPlayStop->setEnabled(false);
  mPlayRecord->setEnabled(pluginValid);
}

void Part::setModified(bool modified)
{
  if (modified)
    mFileSave->setEnabled(true);
  else
    mFileSave->setEnabled(false);

  ReadWritePart::setModified(modified);
}

QString Part::fileFilter(bool write) const
{
  QString filterList = "";
  QString allTypes = "";

  PluginList::const_iterator it = mFileIOPlugins.begin();
  PluginList::const_iterator e  = mFileIOPlugins.end();
  for ( ; it != e; ++it)
  {
    FileIO *p = static_cast<FileIO*>(*it);
    if (write)
      filterList += p->writeFilter();
    else
      filterList += p->readFilter();
  }
  filterList = allTypes + i18n("|All Sound Files\n") + filterList;
  filterList += i18n("*|All Files");

  return filterList;
}

bool Part::openFile()
{
  kdDebug(60606) << m_url.fileName() << endl;
  QString mimeType = KMimeType::findByURL(m_url)->name();

  // TODO: fall back to other plugins
  Sonik::FileIO::Reader* r = mFileIOFactory.makeReader(m_file, mimeType);
  if (r == 0)
  {
    KMessageBox::detailedSorry(widget(),
      i18n("Unable to load files of the format %1.").arg(mimeType),
      i18n("Please ensure that you entered the correct file name and that all plugins are properly installed"));
    return false;
  }

  mData.suspendSignals();
  Sonik::IOResult res = mData.open(*r);
  delete r;

  if (res == Sonik::kFileNotFound)
  {
    // TODO: more details eg permission denied, file not found
    KMessageBox::sorry(widget(),
      i18n("Unable to read from file %1.").arg(m_url.prettyURL()));
    return false;
  }
  else if (res != Sonik::kSuccess)
  {
    KMessageBox::sorry(widget(),
      i18n("Error reading from file %1.").arg(m_url.prettyURL()));
    return false;
  }

  partWidget()->resetView();

  mData.resumeSignals();

  initialized();

  // no need for options dialog on save
  mOptionsDialogNeeded = false;

  return true;
}

bool Part::saveFile()
{
  kdDebug(60606) << "Part: saveFile " << m_file << "\n";

  // if we aren't read-write, return immediately
  if (isReadWrite() == false)
    return false;

  QString mimeType = KMimeType::findByURL(m_url)->name();

  Sonik::FileIO::Writer* w = mFileIOFactory.makeWriter(m_file, mimeType,
                                                       mData.length(),
                                                       mData.channels(),
                                                       mData.sampleRate(),
                                                       mData.bits());
  if (w == 0)
  {
    KMessageBox::detailedSorry(widget(),
      i18n("Unable to save to the format %1.").arg(mimeType),
      i18n("Please ensure that you entered the correct file name and that all plugins are properly installed"));
    return false;
  }

  if (mOptionsDialogNeeded && w->optionsAvailable())
  {
    SaveOptionsDialog dlg(*w, widget(), "fileoptions");
    if (!dlg.exec())
    {
      delete w;
      return false;
    }
  }
  // no need for options dialog on subsequent saves
  // TODO: show dialog for menu save
  mOptionsDialogNeeded = false;

  Sonik::IOResult res = mData.save(*w);
  delete w;

  if (res == Sonik::kFileNotFound)
  {
    KMessageBox::sorry(widget(),
      i18n("Unable to write to file %1.").arg(m_url.prettyURL()));
    return false;
  }
  else if (res != Sonik::kSuccess)
  {
    KMessageBox::sorry(widget(),
      i18n("Error writing to file %1.").arg(m_url.prettyURL()));
    return false;
  }

  return true;
}

void Part::uiFileSaveAs()
{
  KURL origUrl = m_url;
  // this slot is called whenever the File->Save As menu is selected,
  KURL url = KFileDialog::getSaveURL(QString::null, fileFilter(true),
                                     widget(), i18n("Save File..."));

  if (!url.isEmpty())
  {
    bool doSave = true;

    if (KIO::NetAccess::exists(url, false, widget()))
    {
      int result = KMessageBox::warningContinueCancel(
        widget(),
        i18n( "A file named \"%1\" already exists. "
              "Are you sure you want to overwrite it?" ).arg( url.prettyURL() ),
        i18n( "Overwrite File?" ),
        i18n( "Overwrite" )
        );

      if (result != KMessageBox::Continue)
        doSave = false;
    }

    // force options dialog
    // TODO: make this part of save as dialog - theres a race here as the
    // target file could be created by some other app while options dialog is
    // shown and then overwritten
    mOptionsDialogNeeded = true;

    if (doSave && saveAs(url))
      emit urlChanged(url);
    else // restore original name
    {
      m_url = origUrl;
      emit setWindowCaption(m_url.prettyURL());
    }
  }
}

void Part::uiEditUndo()
{
  mActionManager.undo();
  // TODO: clear modified state if undo/redo back to saved
}

void Part::uiEditRedo()
{
  mActionManager.redo();
}

void Part::uiEditCut()
{
  if (partWidget()->selectionLength() > 0)
  {
    uint start = partWidget()->selectionStart();
    uint length = partWidget()->selectionLength();
    SampleSegment data(mData.channels(), length);

    mData.data(start, length, data);
    QApplication::clipboard()->setData(new AudioDragObject(data));

    mActionManager.beginCompoundAction(i18n("Cut"));
    partWidget()->select(start, 0);
    mData.remove(start, length);
    mActionManager.endCompoundAction();
  }
}

void Part::uiEditCopy()
{
  if (partWidget()->selectionLength() > 0)
  {
    uint start = partWidget()->selectionStart();
    uint length = partWidget()->selectionLength();
    SampleSegment data(mData.channels(), length);

    mData.data(start, length, data);
    QApplication::clipboard()->setData(new AudioDragObject(data));
  }
}

void Part::uiEditPaste()
{
  AudioDragObject* clipObject =
    dynamic_cast<AudioDragObject*>(QApplication::clipboard()->data());

  if (clipObject == 0)
  {
    // TODO: error message
    return;
  }

  // TODO: sample rate warning

  uint start = partWidget()->selectionStart();
  uint length = partWidget()->selectionLength();

  const SampleSegment& clipData = clipObject->data();

  if (clipData.rows() != mData.channels())
  {
    // TODO: only copy 1st n channels
    return;
  }

  mActionManager.beginCompoundAction(i18n("Paste"));

  if (length)
    mData.remove(start, length);
  mData.insert(start, clipData.size());
  mData.setData(start, clipData);

  partWidget()->select(start, clipData.size());

  mActionManager.endCompoundAction();
}

void Part::uiEditDelete()
{
  off_t start = partWidget()->selectionStart();
  size_t length = partWidget()->selectionLength();

  // TODO: only enable action when selection is active
  if (length > 0)
  {
    mActionManager.beginCompoundAction(i18n("Delete"));
    partWidget()->select(start, 0);
    mData.remove(start, length);
    mActionManager.endCompoundAction();
  }
}

void Part::uiEditCrop()
{
  off_t start = partWidget()->selectionStart();
  size_t length = partWidget()->selectionLength();

  // TODO: only enable action when selection is active
  if (length > 0)
  {
    mActionManager.beginCompoundAction(i18n("Crop"));
    partWidget()->select(0, 0);
    mData.remove(start+length, mData.length()-start-length);
    mData.remove(0, start);
    mActionManager.endCompoundAction();
  }
}

void Part::uiEditSelectAll()
{
  mActionManager.beginCompoundAction(i18n("Select All"));
  partWidget()->select(0, mData.length());
  mActionManager.endCompoundAction();
}

void Part::uiEditSelectNone()
{
  mActionManager.beginCompoundAction(i18n("Select None"));
  partWidget()->select(0, 0);
  mActionManager.endCompoundAction();
}

void Part::uiEditSelectInvert()
{
  // TODO: implement this
}

void Part::uiViewZoomIn()
{
  partWidget()->zoomIn();
}

void Part::uiViewZoomOut()
{
  partWidget()->zoomOut();
}

void Part::uiViewTimeFormatChanged()
{
  if (mViewTimeFormatSamples->isChecked())
    setTimeFormat(kSamples);
  else if (mViewTimeFormatTime->isChecked())
    setTimeFormat(kH_M_S_Ms);
  else if (mViewTimeFormatMillisec->isChecked())
    setTimeFormat(kMs);
  else if (mViewTimeFormatCDDAFrames->isChecked())
    setTimeFormat(kH_M_S_FCDDA);
  else if (mViewTimeFormatPALFrames->isChecked())
    setTimeFormat(kH_M_S_FPAL);
  else if (mViewTimeFormatNTSCFrames->isChecked())
    setTimeFormat(kH_M_S_FNTSC);
}

void Part::uiViewValueFormatChanged()
{
  if (mViewValueFormatNormalised->isChecked())
    setValueFormat(kNormalised);
  else if (mViewValueFormatReal->isChecked())
    setValueFormat(kValue);
  else if (mViewValueFormatDb->isChecked())
    setValueFormat(kValueDB);
}

void Part::uiPlayStart()
{
  AudioIOManager::State state = mAudioIOManager.state();
  if (state == AudioIOManager::kPlaying ||
      state == AudioIOManager::kPaused)
    seek(mAudioIOManager.rangeStart());
  else if (state == AudioIOManager::kIdle)
    // TODO: move active selection edge
    partWidget()->select(0, 0);
}

void Part::uiPlayBack()
{
  off_t skip = QMAX(1, (off_t)(1 / partWidget()->zoom()));

  AudioIOManager::State state = mAudioIOManager.state();
  if (state == AudioIOManager::kPlaying ||
      state == AudioIOManager::kPaused)
    seek(mAudioIOManager.position() - skip * kSkipSize);
  else if (state == AudioIOManager::kIdle)
    // TODO: move active selection edge
    partWidget()->select(partWidget()->selectionStart() - skip, 0);
}

void Part::uiPlayForward()
{
  off_t skip = QMAX(1, (off_t)(1 / partWidget()->zoom()));

  AudioIOManager::State state = mAudioIOManager.state();
  if (state == AudioIOManager::kPlaying ||
      state == AudioIOManager::kPaused)
    seek(mAudioIOManager.position() + skip * kSkipSize);
  else if (state == AudioIOManager::kIdle)
    // TODO: move active selection edge
    partWidget()->select(partWidget()->selectionStart() + skip, 0);
}

void Part::uiPlayEnd()
{
  AudioIOManager::State state = mAudioIOManager.state();
  if (state == AudioIOManager::kPlaying ||
      state == AudioIOManager::kPaused)
    seek(mAudioIOManager.rangeStart() + mAudioIOManager.rangeLength());
  else if (state == AudioIOManager::kIdle)
    // TODO: move active selection edge
    partWidget()->select(mData.length(), 0);
}

void Part::uiPlayPlay()
{
  play(false);
}

void Part::uiPlayPlayLooped()
{
  play(true);
}

void Part::uiPlayPause()
{
  mAudioIOManager.pause();
}

void Part::uiPlayStop()
{
  stop();
}

void Part::uiPlayRecord()
{
  if (!mRecordDialog)
  {
    mRecordDialog = new Sonik::RecordDialog(*this, widget(), "sonik_record");
    connect(mRecordDialog, SIGNAL(record()),
            this, SLOT(recordDialogRecord()));
    connect(mRecordDialog, SIGNAL(stop()),
            this, SLOT(recordDialogStop()));
    connect(mRecordDialog, SIGNAL(finished()),
            this, SLOT(recordDialogFinished()));
    connect(this, SIGNAL(selectionChanged(off_t, size_t)),
            mRecordDialog, SLOT(selectionChanged(off_t, size_t)));
    connect(&mAudioIOManager, SIGNAL(position(off_t)),
            mRecordDialog, SLOT(position(off_t)));
    mRecordDialog->show();
  }
  else
  {
    KWin::activateWindow(mRecordDialog->winId());
  }
}

void Part::recordDialogRecord()
{
  // TODO: playback pos

  record(mRecordDialog->action());
}

void Part::recordDialogStop()
{
  stop();
}

void Part::recordDialogFinished()
{
  mRecordDialog->hide();
  mRecordDialog->deleteLater();
  mRecordDialog = 0;
}

void Part::play(bool looping)
{
  AudioIOManager::State state = mAudioIOManager.state();

  if (!mActiveAudioIOPlugin ||
      (state != AudioIOManager::kIdle && state != AudioIOManager::kPaused))
    return;

  if (mAudioIOManager.open(mActiveAudioIOPlugin) != Sonik::kSuccess)
  {
    KMessageBox::error(widget(), i18n("Error opening audio device"));
    return;
  }

  if (partWidget()->selectionLength() > 0)
    mAudioIOManager.setRange(partWidget()->selectionStart(),
                             partWidget()->selectionLength());
  else
    mAudioIOManager.setRange(0, mData.length());

  mAudioIOManager.seek(partWidget()->selectionStart());
  mAudioIOManager.play(looping);
}

void Part::record(InsertPosition action)
{
  mActionManager.beginCompoundAction(i18n("Record"));

  switch (action)
  {
    case Sonik::kReplaceAll:
    case Sonik::kInsertStart:
      mRecordStart = 0;
      break;

    case Sonik::kInsertCursor:
    case Sonik::kOverwriteCursor:
    case Sonik::kReplaceSelection:
      mRecordStart = partWidget()->selectionStart();
      break;

    case Sonik::kInsertEnd:
      mRecordStart = mData.length();
      break;

    default:
      // Not valid
      mActionManager.abortCompoundAction();
      return;
  }

  off_t  origSelStart  = partWidget()->selectionStart();
  size_t origSelLength = partWidget()->selectionLength();

  partWidget()->select(mRecordStart, 0);

  if (action == kReplaceAll)
    mData.remove(0, mData.length());
  else if (action == kOverwriteCursor)
    mData.remove(origSelStart, mData.length() - origSelStart);
  else if (action == kReplaceSelection)
    mData.remove(origSelStart, origSelLength);

  if (mData.length() == 0)
    mData.setFormat(mRecordDialog->format());

  mAudioIOManager.setRange(0, mData.length());

  if (!mActiveAudioIOPlugin ||
      mAudioIOManager.state() != AudioIOManager::kIdle ||
      mAudioIOManager.open(mActiveAudioIOPlugin) != Sonik::kSuccess)
  {
    mActionManager.abortCompoundAction();

    KMessageBox::error(widget(), i18n("Error opening audio device"));
    return;
  }

  seek(mRecordStart);

  mData.suspendSignals(); // stop UI updates killing audioIO

  mAudioIOManager.record();
}

void Part::stop()
{
  AudioIOManager::State state = mAudioIOManager.state();

  mAudioIOManager.stop();

  if (state == AudioIOManager::kRecording)
  {
    mData.resumeSignals();
    mActionManager.endCompoundAction();
  }

  mAudioIOManager.close();
}

void Part::seek(off_t pos)
{
  mAudioIOManager.seek(pos);
}

void Part::contextMenu(ContextMenuType type, const QPoint& pos)
{
  QPopupMenu *menu = NULL;

  // TODO: move to widget

  switch (type)
  {
    case kWaveWidget:
      menu = static_cast<QPopupMenu*>(factory()->container("popup_main", this));
      break;

    case kTimeScale:
      menu = static_cast<QPopupMenu*>(factory()->container("popup_time_scale", this));
      break;

    case kValueScale:
      menu = static_cast<QPopupMenu*>(factory()->container("popup_value_scale", this));
      break;

    default:
      break;
  }

  if (menu)
    menu->popup(pos, 0);
}

bool Part::formatDialog(QWidget *parent)
{
  FormatDialog formatDlg(mData.format(), parent);

  if (formatDlg.exec())
  {
    mData.setFormat(formatDlg.format());
    return true;
  }

  return false;
}

void Part::uiSettingsConfigure()
{
  // open a config dialog or switch to existing one
  if (!mConfigDlg)
  {
    mConfigDlg = new ConfigDialog(*this, widget(), "sonik_preferences");
    connect(mConfigDlg, SIGNAL(finished()),
            this, SLOT(configDialogFinished()));
    mConfigDlg->show();
  }
  else
  {
    KWin::activateWindow(mConfigDlg->winId());
  }
}

void Part::configDialogFinished()
{
  mConfigDlg->delayedDestruct();
  mConfigDlg = 0;
}

void Part::selectDisplayPlugin(Sonik::Display* plugin)
{
  PluginList::const_iterator it = mDisplayPlugins.begin();
  PluginList::const_iterator e  = mDisplayPlugins.end();
  for ( ; it != e; ++it)
  {
    Sonik::Display *p = static_cast<Sonik::Display*>(*it);
    if (plugin->pluginName() != p->pluginName())
      p->setActive(false);
  }

  plugin->setActive(true);
  partWidget()->setDisplay(plugin);
  if (plugin->verticalAxis() == Sonik::Display::kValue)
  {
    mViewValueFormatNormalised->setEnabled(true);
    mViewValueFormatReal->setEnabled(true);
    mViewValueFormatDb->setEnabled(true);
  }
  else
  {
    mViewValueFormatNormalised->setEnabled(false);
    mViewValueFormatReal->setEnabled(false);
    mViewValueFormatDb->setEnabled(false);
  }
  uiViewValueFormatChanged();
}

void Part::initialized()
{
  setModified(false);
  mActionManager.clear();
}

//
// Data signal handlers
//

void Part::dataChannelsChanged(uint8_t /*channels*/)
{
  setModified(true);
}

void Part::dataLengthChanged(size_t /*length*/)
{
  setModified(true);
}

void Part::dataSampleRateChanged(uint32_t /*sampleRate*/)
{
  setModified(true);
}

void Part::dataBitsChanged(uint8_t /*bits*/)
{
  setModified(true);
}

void Part::dataDataChanged(uint8_t /*c*/, off_t /*start*/, size_t /*length*/)
{
  setModified(true);

  if (!isReadWrite())
  {
    //TODO: error
    return;
  }
}


//
// View signal handlers
//

void Part::viewSelectionChanged(off_t start, size_t length)
{
  if (length > 0)
    mAudioIOManager.setRange(start, length);
  else
    mAudioIOManager.setRange(0, mData.length());

  AudioIOManager::State state = mAudioIOManager.state();
  if (state == AudioIOManager::kPlaying ||
      state == AudioIOManager::kPaused)
    seek(start);
}

void Part::viewDisplaySelectionChanged(off_t, size_t)
{
}

void Part::viewCursorPosChanged(off_t)
{
}

void Part::viewPlaybackPosChanged(off_t)
{
}


//
// Audio IO signal handlers
//

void Part::audioIOPlaying()
{
  mPlayPause->setEnabled(true && mActiveAudioIOPlugin);
  mPlayStop->setEnabled(true && mActiveAudioIOPlugin);
  mPlayPlay->setEnabled(false);
  mPlayPlayLooped->setEnabled(false);
  mPlayRecord->setEnabled(false);
}

void Part::audioIORecording()
{
  mPlayPause->setEnabled(false);
  mPlayStop->setEnabled(true && mActiveAudioIOPlugin);
  mPlayPlay->setEnabled(false);
  mPlayPlayLooped->setEnabled(false);
  mPlayRecord->setEnabled(false);

  mPlayStart->setEnabled(false);
  mPlayForward->setEnabled(false);
  mPlayBack->setEnabled(false);
  mPlayEnd->setEnabled(false);
}

void Part::audioIOPaused()
{
  mPlayPause->setEnabled(true);
  mPlayStop->setEnabled(false);
  mPlayPlay->setEnabled(true && mActiveAudioIOPlugin);
  mPlayPlayLooped->setEnabled(true && mActiveAudioIOPlugin);
}

void Part::audioIOStopped()
{
  mPlayPause->setEnabled(false);
  mPlayStop->setEnabled(false);
  mPlayPlay->setEnabled(true && mActiveAudioIOPlugin);
  mPlayPlayLooped->setEnabled(true && mActiveAudioIOPlugin);
  mPlayRecord->setEnabled(true && mActiveAudioIOPlugin);

  mPlayStart->setEnabled(true);
  mPlayForward->setEnabled(true);
  mPlayBack->setEnabled(true);
  mPlayEnd->setEnabled(true);

  // select new recording
  if (mRecordStart != -1)
  {
    partWidget()->select(mRecordStart, mLastPos - mRecordStart + 1);
    mRecordStart = -1;
  }
}

void Part::audioIOPosition(off_t pos)
{
  mLastPos = pos;
}

void Part::actionManagerUndoChanged(const QString& name)
{
  if (!name.isNull())
  {
    mEditUndo->setEnabled(true);
    mEditUndo->setText(i18n("&Undo") + ": " + name);
  }
  else
  {
    mEditUndo->setEnabled(false);
    mEditUndo->setText(i18n("&Undo"));
  }
}

void Part::actionManagerRedoChanged(const QString& name)
{
  if (!name.isNull())
  {
    mEditRedo->setEnabled(true);
    mEditRedo->setText(i18n("Re&do") + ": " + name);
  }
  else
  {
    mEditRedo->setEnabled(false);
    mEditRedo->setText(i18n("Re&do"));
  }
}

//
// DCOP methods
//

uint Part::cursorPos() const
{
  return partWidget()->cursorPos();
}

uint Part::selectionStart() const
{
  return partWidget()->selectionStart();
}

uint Part::selectionLength() const
{
  return partWidget()->selectionLength();
}
