/* This file is part of the KDE project
   Copyright (C) 1999 Simon Hausmann <hausmann@kde.org>
             (C) 1999 David Faure <faure@kde.org>

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public
   License as published by the Free Software Foundation; either
   version 2 of the License, or (at your option) any later version.

   This library 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
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public License
   along with this library; see the file COPYING.LIB.  If not, write to
   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
   Boston, MA 02110-1301, USA.
*/

#include <tdeparts/part.h>
#include <tdeparts/event.h>
#include <tdeparts/plugin.h>
#include <tdeparts/mainwindow.h>
#include <tdeparts/partmanager.h>

#include <tqapplication.h>
#include <tqfile.h>
#include <tqpoint.h>
#include <tqpointarray.h>
#include <tqpainter.h>
#include <tqtextstream.h>
#include <tqfileinfo.h>

#include <kinstance.h>
#include <tdelocale.h>
#include <tdetempfile.h>
#include <tdemessagebox.h>
#include <tdeio/job.h>
#include <kstandarddirs.h>
#include <tdefiledialog.h>
#include <kdirnotify_stub.h>

#include <stdio.h>
#include <unistd.h>
#include <assert.h>
#include <kdebug.h>

template class TQPtrList<KXMLGUIClient>;

using namespace KParts;

namespace KParts
{

class PartBasePrivate
{
public:
  PartBasePrivate()
  {
      m_pluginLoadingMode = PartBase::LoadPlugins;
  }
  ~PartBasePrivate()
  {
  }
  PartBase::PluginLoadingMode m_pluginLoadingMode;
};

class PartPrivate
{
public:
  PartPrivate()
  {
    m_bSelectable = true;
  }
  ~PartPrivate()
  {
  }

  bool m_bSelectable;
};
}

PartBase::PartBase()
{
  d = new PartBasePrivate;
  m_obj = 0L;
}

PartBase::~PartBase()
{
  delete d;
}

void PartBase::setPartObject( TQObject *obj )
{
  m_obj = obj;
}

TQObject *PartBase::partObject() const
{
  return m_obj;
}

void PartBase::setInstance( TDEInstance *inst )
{
  setInstance( inst, true );
}

void PartBase::setInstance( TDEInstance *inst, bool bLoadPlugins )
{
  KXMLGUIClient::setInstance( inst );
  TDEGlobal::locale()->insertCatalogue( inst->instanceName() );
  // install 'instancename'data resource type
  TDEGlobal::dirs()->addResourceType( inst->instanceName() + "data",
                                    TDEStandardDirs::kde_default( "data" )
                                    + TQString::fromLatin1( inst->instanceName() ) + '/' );
  if ( bLoadPlugins )
    loadPlugins( m_obj, this, instance() );
}

void PartBase::loadPlugins( TQObject *parent, KXMLGUIClient *parentGUIClient, TDEInstance *instance )
{
  if( d->m_pluginLoadingMode != DoNotLoadPlugins )
    Plugin::loadPlugins( parent, parentGUIClient, instance, d->m_pluginLoadingMode == LoadPlugins );
}

void PartBase::setPluginLoadingMode( PluginLoadingMode loadingMode )
{
    d->m_pluginLoadingMode = loadingMode;
}

Part::Part( TQObject *parent, const char* name )
 : TQObject( parent, name )
{
  d = new PartPrivate;
  m_widget = 0L;
  m_manager = 0L;
  PartBase::setPartObject( this );
}

Part::~Part()
{
  kdDebug(1000) << "Part::~Part " << this << endl;

  if ( m_widget )
  {
    // We need to disconnect first, to avoid calling it !
    disconnect( m_widget, TQT_SIGNAL( destroyed() ),
                this, TQT_SLOT( slotWidgetDestroyed() ) );
  }

  if ( m_manager )
    m_manager->removePart(this);

  if ( m_widget )
  {
    kdDebug(1000) << "deleting widget " << m_widget << " " << m_widget->name() << endl;
    delete (TQWidget*) m_widget;
  }

  delete d;
}

void Part::embed( TQWidget * parentWidget )
{
  if ( widget() )
    widget()->reparent( parentWidget, 0, TQPoint( 0, 0 ), true );
}

TQWidget *Part::widget()
{
  return m_widget;
}

void Part::setManager( PartManager *manager )
{
  m_manager = manager;
}

PartManager *Part::manager() const
{
  return m_manager;
}

Part *Part::hitTest( TQWidget *widget, const TQPoint & )
{
  if ( (TQWidget *)m_widget != widget )
    return 0L;

  return this;
}

void Part::setWidget( TQWidget *widget )
{
  assert ( !m_widget ); // otherwise we get two connects
  m_widget = widget;
  connect( m_widget, TQT_SIGNAL( destroyed() ),
           this, TQT_SLOT( slotWidgetDestroyed() ) );

  // Tell the actionCollection() which widget its
  //  action shortcuts should be connected to.
  actionCollection()->setWidget( widget );

  // Since KParts objects are XML-based, shortcuts should
  //  be connected to the widget when the XML settings
  //  are processed, rather than on TDEAction construction.
  actionCollection()->setAutoConnectShortcuts( false );
}

void Part::setSelectable( bool selectable )
{
  d->m_bSelectable = selectable;
}

bool Part::isSelectable() const
{
  return d->m_bSelectable;
}

void Part::customEvent( TQCustomEvent *event )
{
  if ( PartActivateEvent::test( event ) )
  {
    partActivateEvent( (PartActivateEvent *)event );
    return;
  }

  if ( PartSelectEvent::test( event ) )
  {
    partSelectEvent( (PartSelectEvent *)event );
    return;
  }

  if ( GUIActivateEvent::test( event ) )
  {
    guiActivateEvent( (GUIActivateEvent *)event );
    return;
  }

  TQObject::customEvent( event );
}

void Part::partActivateEvent( PartActivateEvent * )
{
}

void Part::partSelectEvent( PartSelectEvent * )
{
}

void Part::guiActivateEvent( GUIActivateEvent * )
{
}

TQWidget *Part::hostContainer( const TQString &containerName )
{
  if ( !factory() )
    return 0L;

  return factory()->container( containerName, this );
}

void Part::slotWidgetDestroyed()
{
  kdDebug(1000) << "KPart::slotWidgetDestroyed(), deleting part " << name() << endl;
  m_widget = 0;
  delete this;
}

//////////////////////////////////////////////////

namespace KParts
{

class ReadOnlyPartPrivate
{
public:
  ReadOnlyPartPrivate()
  {
    m_job = 0L;
    m_uploadJob = 0L;
    m_showProgressInfo = true;
    m_saveOk = false;
    m_waitForSave = false;
    m_duringSaveAs = false;
  }
  ~ReadOnlyPartPrivate()
  {
  }

  TDEIO::FileCopyJob * m_job;
  TDEIO::FileCopyJob * m_uploadJob;
  KURL m_originalURL; // for saveAs
  TQString m_originalFilePath; // for saveAs
  bool m_showProgressInfo : 1;
  bool m_saveOk : 1;
  bool m_waitForSave : 1;
  bool m_duringSaveAs : 1;
};

}

ReadOnlyPart::ReadOnlyPart( TQObject *parent, const char *name )
 : Part( parent, name ), m_bTemp( false )
{
  d = new ReadOnlyPartPrivate;
}

ReadOnlyPart::~ReadOnlyPart()
{
  ReadOnlyPart::closeURL();
  delete d;
}

void ReadOnlyPart::setProgressInfoEnabled( bool show )
{
  d->m_showProgressInfo = show;
}

bool ReadOnlyPart::isProgressInfoEnabled() const
{
  return d->m_showProgressInfo;
}

#ifndef KDE_NO_COMPAT
void ReadOnlyPart::showProgressInfo( bool show )
{
  d->m_showProgressInfo = show;
}
#endif

bool ReadOnlyPart::openURL( const KURL &url )
{
  if ( !url.isValid() )
    return false;
  if ( !closeURL() )
    return false;
  m_url = url;
  if ( m_url.isLocalFile() )
  {
    emit started( 0 );
    m_file = m_url.path();
    bool ret = openFile();
    if (ret)
    {
        emit completed();
        emit setWindowCaption( m_url.prettyURL() );
    };
    return ret;
  }
  else
  {
    m_bTemp = true;
    // Use same extension as remote file. This is important for mimetype-determination (e.g. koffice)
    TQString fileName = url.fileName();
    TQFileInfo fileInfo(fileName);
    TQString ext = fileInfo.extension();
    TQString extension;
    if ( !ext.isEmpty() && url.query().isNull() ) // not if the URL has a query, e.g. cgi.pl?something
        extension = "."+ext; // keep the '.'
    KTempFile tempFile( TQString::null, extension );
    m_file = tempFile.name();

    KURL destURL;
    destURL.setPath( m_file );
    d->m_job = TDEIO::file_copy( m_url, destURL, 0600, true, false, d->m_showProgressInfo );
    d->m_job->setWindow( widget() ? widget()->topLevelWidget() : 0 );
    emit started( d->m_job );
    connect( d->m_job, TQT_SIGNAL( result( TDEIO::Job * ) ), this, TQT_SLOT( slotJobFinished ( TDEIO::Job * ) ) );
    return true;
  }
}

void ReadOnlyPart::abortLoad()
{
  if ( d->m_job )
  {
    //kdDebug(1000) << "Aborting job " << d->m_job << endl;
    d->m_job->kill();
    d->m_job = 0;
  }
}

bool ReadOnlyPart::closeURL()
{
  abortLoad(); //just in case

  if ( m_bTemp )
  {
    unlink( TQFile::encodeName(m_file) );
    m_bTemp = false;
  }
  // It always succeeds for a read-only part,
  // but the return value exists for reimplementations
  // (e.g. pressing cancel for a modified read-write part)
  return true;
}

void ReadOnlyPart::slotJobFinished( TDEIO::Job * job )
{
  kdDebug(1000) << "ReadOnlyPart::slotJobFinished" << endl;
  assert( job == d->m_job );
  d->m_job = 0;
  if (job->error())
    emit canceled( job->errorString() );
  else
  {
    if ( openFile() )
      emit setWindowCaption( m_url.prettyURL() );
    emit completed();
  }
}

void ReadOnlyPart::guiActivateEvent( GUIActivateEvent * event )
{
  if (event->activated())
  {
    if (!m_url.isEmpty())
    {
      kdDebug(1000) << "ReadOnlyPart::guiActivateEvent -> " << m_url.prettyURL() << endl;
      emit setWindowCaption( m_url.prettyURL() );
    } else emit setWindowCaption( "" );
  }
}

bool ReadOnlyPart::openStream( const TQString& mimeType, const KURL& url )
{
    if ( !closeURL() )
        return false;
    m_url = url;
    return doOpenStream( mimeType );
}

bool ReadOnlyPart::writeStream( const TQByteArray& data )
{
    return doWriteStream( data );
}

bool ReadOnlyPart::closeStream()
{
    return doCloseStream();
}

//////////////////////////////////////////////////

ReadWritePart::ReadWritePart( TQObject *parent, const char *name )
 : ReadOnlyPart( parent, name ), m_bModified( false ), m_bClosing( false )
{
  m_bReadWrite = true;
}

ReadWritePart::~ReadWritePart()
{
  // parent destructor will delete temp file
  // we can't call our own closeURL() here, because
  // "cancel" wouldn't cancel anything. We have to assume
  // the app called closeURL() before destroying us.
}

void ReadWritePart::setReadWrite( bool readwrite )
{
  // Perhaps we should check isModified here and issue a warning if true
  m_bReadWrite = readwrite;
}

void ReadWritePart::setModified( bool modified )
{
  kdDebug(1000) << "ReadWritePart::setModified( " << (modified ? "true" : "false") << ")" << endl;
  if ( !m_bReadWrite && modified )
  {
      kdError(1000) << "Can't set a read-only document to 'modified' !" << endl;
      return;
  }
  m_bModified = modified;
}

void ReadWritePart::setModified()
{
  setModified( true );
}

bool ReadWritePart::queryClose()
{
  if ( !isReadWrite() || !isModified() )
    return true;

  TQString docName = url().fileName();
  if (docName.isEmpty()) docName = i18n( "Untitled" );

  int res = KMessageBox::warningYesNoCancel( widget(),
          i18n( "The document \"%1\" has been modified.\n"
                "Do you want to save your changes or discard them?" ).arg( docName ),
          i18n( "Close Document" ), KStdGuiItem::save(), KStdGuiItem::discard() );

  bool abortClose=false;
  bool handled=false;

  switch(res) {
  case KMessageBox::Yes :
    sigQueryClose(&handled,&abortClose);
    if (!handled)
    {
      if (m_url.isEmpty())
      {
          KURL url = KFileDialog::getSaveURL();
          if (url.isEmpty())
            return false;

          saveAs( url );
      }
      else
      {
          save();
      }
    } else if (abortClose) return false;
    return waitSaveComplete();
  case KMessageBox::No :
    return true;
  default : // case KMessageBox::Cancel :
    return false;
  }
}

bool ReadWritePart::closeURL()
{
  abortLoad(); //just in case
  if ( isReadWrite() && isModified() )
  {
    if (!queryClose())
       return false;
  }
  // Not modified => ok and delete temp file.
  return ReadOnlyPart::closeURL();
}

bool ReadWritePart::closeURL( bool promptToSave )
{
  return promptToSave ? closeURL() : ReadOnlyPart::closeURL();
}

bool ReadWritePart::save()
{
  d->m_saveOk = false;
  if ( m_file.isEmpty() ) // document was created empty
      prepareSaving();
  if( saveFile() )
    return saveToURL();
  else
    emit canceled(TQString::null);
  return false;
}

bool ReadWritePart::saveAs( const KURL & kurl )
{
  if (!kurl.isValid())
  {
      kdError(1000) << "saveAs: Malformed URL " << kurl.url() << endl;
      return false;
  }
  d->m_duringSaveAs = true;
  d->m_originalURL = m_url;
  d->m_originalFilePath = m_file;
  m_url = kurl; // Store where to upload in saveToURL
  prepareSaving();
  bool result = save(); // Save local file and upload local file
  if (result)
    emit setWindowCaption( m_url.prettyURL() );
  else
  {
    m_url = d->m_originalURL;
    m_file = d->m_originalFilePath;
    d->m_duringSaveAs = false;
    d->m_originalURL = KURL();
    d->m_originalFilePath = TQString::null;
  }

  return result;
}

// Set m_file correctly for m_url
void ReadWritePart::prepareSaving()
{
  // Local file
  if ( m_url.isLocalFile() )
  {
    if ( m_bTemp ) // get rid of a possible temp file first
    {              // (happens if previous url was remote)
      unlink( TQFile::encodeName(m_file) );
      m_bTemp = false;
    }
    m_file = m_url.path();
  }
  else
  { // Remote file
    // We haven't saved yet, or we did but locally - provide a temp file
    if ( m_file.isEmpty() || !m_bTemp )
    {
      KTempFile tempFile;
      m_file = tempFile.name();
      m_bTemp = true;
    }
    // otherwise, we already had a temp file
  }
}

bool ReadWritePart::saveToURL()
{
  if ( m_url.isLocalFile() )
  {
    setModified( false );
    emit completed();
    // if m_url is a local file there won't be a temp file -> nothing to remove
    assert( !m_bTemp );
    d->m_saveOk = true;
    d->m_duringSaveAs = false;
    d->m_originalURL = KURL();
    d->m_originalFilePath = TQString::null;
    return true; // Nothing to do
  }
  else
  {
    if (d->m_uploadJob)
    {
       unlink(TQFile::encodeName(d->m_uploadJob->srcURL().path()));
       d->m_uploadJob->kill();
       d->m_uploadJob = 0;
    }
    KTempFile tempFile;
    TQString uploadFile = tempFile.name();
    KURL uploadUrl;
    uploadUrl.setPath( uploadFile );
    tempFile.unlink();
    // Create hardlink
    if (::link(TQFile::encodeName(m_file), TQFile::encodeName(uploadFile)) != 0)
    {
       // Uh oh, some error happened.
       return false;
    }
    d->m_uploadJob = TDEIO::file_move( uploadUrl, m_url, -1, true /*overwrite*/ );
    d->m_uploadJob->setWindow( widget() ? widget()->topLevelWidget() : 0 );
    connect( d->m_uploadJob, TQT_SIGNAL( result( TDEIO::Job * ) ), this, TQT_SLOT( slotUploadFinished (TDEIO::Job *) ) );
    return true;
  }
}

void ReadWritePart::slotUploadFinished( TDEIO::Job * )
{
  if (d->m_uploadJob->error())
  {
    unlink(TQFile::encodeName(d->m_uploadJob->srcURL().path()));
    TQString error = d->m_uploadJob->errorString();
    d->m_uploadJob = 0;
    if (d->m_duringSaveAs) {
      m_url = d->m_originalURL;
      m_file = d->m_originalFilePath;
    }
    emit canceled( error );
  }
  else
  {
    KDirNotify_stub allDirNotify("*", "KDirNotify*");
    KURL dirUrl( m_url );
    dirUrl.setPath( dirUrl.directory() );
    allDirNotify.FilesAdded( dirUrl );

    d->m_uploadJob = 0;
    setModified( false );
    emit completed();
    d->m_saveOk = true;
  }
  d->m_duringSaveAs = false;
  d->m_originalURL = KURL();
  d->m_originalFilePath = TQString::null;
  if (d->m_waitForSave)
  {
     tqApp->exit_loop();
  }
}

// Trolls: Nothing to see here, please step away.
void tqt_enter_modal( TQWidget *widget );
void tqt_leave_modal( TQWidget *widget );

bool ReadWritePart::waitSaveComplete()
{
  if (!d->m_uploadJob)
     return d->m_saveOk;

  d->m_waitForSave = true;

  TQWidget dummy(0,0,(WFlags)(WType_Dialog | WShowModal));
  dummy.setFocusPolicy( TQ_NoFocus );
  tqt_enter_modal(&dummy);
  tqApp->enter_loop();
  tqt_leave_modal(&dummy);

  d->m_waitForSave = false;

  return d->m_saveOk;
}

#include "part.moc"

// vim:sw=2:ts=8:et