/* This file is part of the KDE libraries
    Copyright (C) 1999,2000,2001 Carsten Pfeiffer <pfeiffer@kde.org>

    library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License version 2, as published by the Free Software Foundation.

    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 "kurlrequester.h"

#include <kcombobox.h>
#include <kfiledialog.h>
#include <klineedit.h>
#include <klocale.h>
#include <kurlcompletion.h>
#include <kprotocolmanager.h>
#include <khbox.h>
#include <kstandardshortcut.h>
#include <kdebug.h>

#include <QEvent>
#include <QDrag>
#include <QMimeData>
#include <QAction>
#include <QApplication>
#include <QDir>

class KUrlDragPushButton : public KPushButton
{
public:
    KUrlDragPushButton( QWidget *parent)
        : KPushButton( parent)
    {
        setDragEnabled( true );
    }
    ~KUrlDragPushButton() {}

    void setURL( const KUrl& url )
    {
        m_urls.clear();
        m_urls.append( url );
    }

protected:
    virtual QDrag *dragObject()
    {
        if (m_urls.isEmpty())
            return 0;

        QDrag *drag = new QDrag(this);
        QMimeData *mimeData = new QMimeData;
        m_urls.populateMimeData(mimeData);
        drag->setMimeData(mimeData);
        return drag;
    }

private:
    KUrl::List m_urls;

};


class KUrlRequester::KUrlRequesterPrivate
{
public:
    KUrlRequesterPrivate(KUrlRequester *parent)
        : m_parent(parent),
          edit(0),
          combo(0),
          fileDialogMode(KFile::File | KFile::ExistingOnly | KFile::LocalOnly)
    {
    }

    ~KUrlRequesterPrivate()
    {
        delete myCompletion;
        delete myFileDialog;
    }

    void init();

    void setText( const QString& text ) {
        if ( combo )
        {
            if (combo->isEditable())
            {
               combo->setEditText( text );
            }
            else
            {
               int i = combo->findText( text );
               if ( i == -1 )
               {
                  combo->addItem( text );
                  combo->setCurrentIndex( combo->count()-1 );
               }
               else
               {
                  combo->setCurrentIndex( i );
               }
            }
        }
        else
        {
            edit->setText( text );
        }
    }

    void connectSignals( QObject *receiver )
    {
        QObject *sender;
        if ( combo )
            sender = combo;
        else
            sender = edit;

        if (combo )
            connect( sender, SIGNAL(editTextChanged(QString)),
                     receiver, SIGNAL(textChanged(QString)));
        else
            connect( sender, SIGNAL(textChanged(QString)),
                     receiver, SIGNAL(textChanged(QString)));

        connect( sender, SIGNAL(returnPressed()),
                 receiver, SIGNAL(returnPressed()));
        connect( sender, SIGNAL(returnPressed(QString)),
                 receiver, SIGNAL(returnPressed(QString)));
    }

    void setCompletionObject( KCompletion *comp )
    {
        if ( combo )
            combo->setCompletionObject( comp );
        else
            edit->setCompletionObject( comp );
    }

    void updateCompletionStartDir( const KUrl &newStartDir )
    {
        if ( newStartDir.isLocalFile() )
            myCompletion->setDir(newStartDir.toLocalFile());
    }

    QString text() const {
        return combo ? combo->currentText() : edit->text();
    }

    /**
     * replaces ~user or $FOO, if necessary
     * if text() is a relative path, make it absolute using startDir()
     */
    KUrl url() const {
        const QString txt = text();
        KUrlCompletion *comp;
        if ( combo )
            comp = qobject_cast<KUrlCompletion*>(combo->completionObject());
        else
            comp = qobject_cast<KUrlCompletion*>(edit->completionObject());

        KUrl enteredPath;
        KUrl baseDir(m_startDir);
        if ( comp )
            enteredPath = KUrl( comp->replacedPath( txt ) );
        else
            enteredPath = KUrl( txt );

        if ( enteredPath.isRelative() && !txt.isEmpty() ) {
            baseDir.addPath(enteredPath.path());
            return baseDir;
        } else {
            return enteredPath;
        }
    }

    // slots
    void _k_slotUpdateUrl();
    void _k_slotOpenDialog();
    void _k_slotFileDialogFinished();

    KUrl m_startDir;
    bool m_startDirCustomized;
    KUrlRequester *m_parent;
    KLineEdit *edit;
    KComboBox *combo;
    KFile::Modes fileDialogMode;
    QString fileDialogFilter;
#ifndef KDE_NO_DEPRECATED
    KEditListBox::CustomEditor editor;
#else
    KEditListWidget::CustomEditor editor;
#endif
    KUrlDragPushButton *myButton;
    KFileDialog *myFileDialog;
    KUrlCompletion *myCompletion;
    Qt::WindowModality fileDialogModality;
};



KUrlRequester::KUrlRequester( QWidget *editWidget, QWidget *parent)
  : KHBox( parent),d(new KUrlRequesterPrivate(this))
{
    // must have this as parent
    editWidget->setParent( this );
    d->combo = qobject_cast<KComboBox*>( editWidget );
    d->edit = qobject_cast<KLineEdit*>( editWidget );
    if ( d->edit ) {
        d->edit->setClearButtonShown( true );
    }

    d->init();
}


KUrlRequester::KUrlRequester( QWidget *parent)
  : KHBox( parent),d(new KUrlRequesterPrivate(this))
{
    d->init();
}


KUrlRequester::KUrlRequester( const KUrl& url, QWidget *parent)
  : KHBox( parent),d(new KUrlRequesterPrivate(this))
{
    d->init();
    setUrl( url );
}

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


void KUrlRequester::KUrlRequesterPrivate::init()
{
    m_parent->setMargin(0);
    m_parent->setSpacing(-1); // use default spacing

    myFileDialog = 0L;
    fileDialogModality = Qt::ApplicationModal;

    if ( !combo && !edit ) {
        edit = new KLineEdit( m_parent );
        edit->setClearButtonShown( true );
    }

    QWidget *widget = combo ? (QWidget*) combo : (QWidget*) edit;

    myButton = new KUrlDragPushButton(m_parent);
    myButton->setIcon(KIcon("document-open"));
    int buttonSize = myButton->sizeHint().expandedTo(widget->sizeHint()).height();
    myButton->setFixedSize(buttonSize, buttonSize);
    myButton->setToolTip(i18n("Open file dialog"));

    m_parent->connect(myButton, SIGNAL(pressed()), SLOT(_k_slotUpdateUrl()));

    widget->installEventFilter( m_parent );
    m_parent->setFocusProxy( widget );
    m_parent->setFocusPolicy(Qt::StrongFocus);

    connectSignals( m_parent );
    m_parent->connect(myButton, SIGNAL(clicked()), m_parent, SLOT(_k_slotOpenDialog()));

    m_startDir = KUrl::fromPath(QDir::currentPath());
    m_startDirCustomized = false;

    myCompletion = new KUrlCompletion();
    updateCompletionStartDir(m_startDir);
    setCompletionObject( myCompletion );

    QAction* openAction = new QAction(m_parent);
    openAction->setShortcut(KStandardShortcut::Open);
    m_parent->connect(openAction, SIGNAL(triggered(bool)), SLOT(_k_slotOpenDialog()));
}

void KUrlRequester::setUrl( const KUrl& url )
{
    d->setText( url.pathOrUrl() );
}

#ifndef KDE_NO_DEPRECATED
void KUrlRequester::setPath( const QString& path )
{
    d->setText( path );
}
#endif

void KUrlRequester::setText(const QString& text)
{
    d->setText(text);
}

void KUrlRequester::setStartDir(const KUrl& startDir)
{
    d->m_startDir = startDir;
    d->m_startDirCustomized = true;
    d->updateCompletionStartDir(startDir);
}

void KUrlRequester::changeEvent(QEvent *e)
{
   if (e->type()==QEvent::WindowTitleChange) {
     if (d->myFileDialog) {
        d->myFileDialog->setCaption(windowTitle());
     }
   }
   KHBox::changeEvent(e);
}

KUrl KUrlRequester::url() const
{
    return d->url();
}

KUrl KUrlRequester::startDir() const
{
    return d->m_startDir;
}

QString KUrlRequester::text() const
{
    return d->text();
}

void KUrlRequester::KUrlRequesterPrivate::_k_slotOpenDialog()
{
    if ( myFileDialog )
        if ( myFileDialog->isVisible() )
        {
            //The file dialog is already being shown, raise it and exit
            myFileDialog->raise();
            myFileDialog->activateWindow();
            return;
        }

    if ( ((fileDialogMode & KFile::Directory) && !(fileDialogMode & KFile::File)) ||
         /* catch possible fileDialog()->setMode( KFile::Directory ) changes */
         (myFileDialog && (myFileDialog->mode() & KFile::Directory) &&
                          (myFileDialog->mode() & (KFile::File | KFile::Files)) == 0) )
    {
        const KUrl openUrl = (!m_parent->url().isEmpty() && !m_parent->url().isRelative() )
          ? m_parent->url() : m_startDir;

        /* FIXME We need a new abstract interface for using KDirSelectDialog in a non-modal way */

        KUrl newurl;
        if (fileDialogMode & KFile::LocalOnly)
            newurl = KFileDialog::getExistingDirectory( openUrl, m_parent);
        else
            newurl = KFileDialog::getExistingDirectoryUrl( openUrl, m_parent);

        if ( newurl.isValid() )
        {
            m_parent->setUrl( newurl );
            emit m_parent->urlSelected( url() );
        }
    }
    else
    {
        emit m_parent->openFileDialog( m_parent );

        //Creates the fileDialog if it doesn't exist yet
        KFileDialog *dlg = m_parent->fileDialog();

        if ( !url().isEmpty() && !url().isRelative() ) {
          KUrl u( url() );
          // If we won't be able to list it (e.g. http), then don't try :)
          if ( KProtocolManager::supportsListing( u ) )
              dlg->setSelection( u.url() );
        } else {
          dlg->setUrl(m_startDir);
        }

        //Update the file dialog window modality
        if ( dlg->windowModality() != fileDialogModality )
            dlg->setWindowModality(fileDialogModality);

        if ( fileDialogModality == Qt::NonModal )
        {
            dlg->show();
        } else {
            dlg->exec();
        }
    }
}

void KUrlRequester::KUrlRequesterPrivate::_k_slotFileDialogFinished()
{
    if ( !myFileDialog )
        return;

    if ( myFileDialog->result() == QDialog::Accepted )
    {
        KUrl newUrl = myFileDialog->selectedUrl();
        if ( newUrl.isValid() )
        {
            m_parent->setUrl( newUrl );
            emit m_parent->urlSelected( url() );
            // remember url as defaultStartDir and update startdir for autocompletion
            if ( newUrl.isLocalFile() && !m_startDirCustomized ) {
                m_startDir = newUrl;
                m_startDir.setPath(m_startDir.directory());
                updateCompletionStartDir(m_startDir);
            }
        }
    }
}

void KUrlRequester::setMode( KFile::Modes mode)
{
    Q_ASSERT( (mode & KFile::Files) == 0 );
    d->fileDialogMode = mode;
    if ( (mode & KFile::Directory) && !(mode & KFile::File) )
        d->myCompletion->setMode( KUrlCompletion::DirCompletion );

    if (d->myFileDialog) {
        d->myFileDialog->setMode(d->fileDialogMode);
    }
}

KFile::Modes KUrlRequester::mode() const
{
    return d->fileDialogMode;
}

void KUrlRequester::setFilter(const QString &filter)
{
    d->fileDialogFilter = filter;
    if (d->myFileDialog) {
        d->myFileDialog->setFilter(d->fileDialogFilter);
    }
}

QString KUrlRequester::filter( ) const
{
    return d->fileDialogFilter;
}

KFileDialog * KUrlRequester::fileDialog() const
{
    if (!d->myFileDialog) {
        QWidget *p = parentWidget();
        d->myFileDialog = new KFileDialog(QString(), d->fileDialogFilter, p);
        d->myFileDialog->setMode(d->fileDialogMode);
        d->myFileDialog->setCaption(windowTitle());
        d->myFileDialog->setWindowModality(d->fileDialogModality);
        connect(d->myFileDialog, SIGNAL(finished()), SLOT(_k_slotFileDialogFinished()));
    }

    return d->myFileDialog;
}

void KUrlRequester::clear()
{
    d->setText( QString() );
}

KLineEdit * KUrlRequester::lineEdit() const
{
    return d->edit;
}

KComboBox * KUrlRequester::comboBox() const
{
    return d->combo;
}

void KUrlRequester::KUrlRequesterPrivate::_k_slotUpdateUrl()
{
    KUrl u( KUrl::fromPath( QDir::currentPath() + '/' ), url().url() );
    myButton->setURL( u );
}

bool KUrlRequester::eventFilter( QObject *obj, QEvent *ev )
{
    if ( ( d->edit == obj ) || ( d->combo == obj ) )
    {
        if (( ev->type() == QEvent::FocusIn ) || ( ev->type() == QEvent::FocusOut ))
            // Forward focusin/focusout events to the urlrequester; needed by file form element in khtml
            QApplication::sendEvent( this, ev );
    }
    return QWidget::eventFilter( obj, ev );
}

KPushButton * KUrlRequester::button() const
{
    return d->myButton;
}

KUrlCompletion *KUrlRequester::completionObject() const
{
    return d->myCompletion;
}

void KUrlRequester::setClickMessage(const QString& msg)
{
    if(d->edit)
        d->edit->setClickMessage(msg);
}

QString KUrlRequester::clickMessage() const
{
    if(d->edit)
        return d->edit->clickMessage();
    else
        return QString();
}

Qt::WindowModality KUrlRequester::fileDialogModality() const
{
    return d->fileDialogModality;
}

void KUrlRequester::setFileDialogModality(Qt::WindowModality modality)
{
    d->fileDialogModality = modality;
}

#ifndef KDE_NO_DEPRECATED
const KEditListBox::CustomEditor &KUrlRequester::customEditor()
#else
const KEditListWidget::CustomEditor &KUrlRequester::customEditor()
#endif
{
    setSizePolicy(QSizePolicy( QSizePolicy::Preferred,
                               QSizePolicy::Fixed));

    KLineEdit *edit = d->edit;
    if ( !edit && d->combo )
        edit = qobject_cast<KLineEdit*>( d->combo->lineEdit() );

#ifndef NDEBUG
    if ( !edit ) {
        kWarning() << "KUrlRequester's lineedit is not a KLineEdit!??\n";
    }
#endif

    d->editor.setRepresentationWidget(this);
    d->editor.setLineEdit(edit);
    return d->editor;
}

KUrlComboRequester::KUrlComboRequester( QWidget *parent)
  : KUrlRequester( new KComboBox(false), parent), d(0)
{
}

#include "kurlrequester.moc"
