/* This file is part of the KDE libraries

   Copyright (c) 2000,2001 Dawit Alemayehu <adawit@kde.org>
   Copyright (c) 2000,2001 Carsten Pfeiffer <pfeiffer@kde.org>
   Copyright (c) 2000 Stefan Schimanski <1Stein@gmx.de>

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License (LGPL) 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
   Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser 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 <tqclipboard.h>
#include <tqlistbox.h>
#include <tqpopupmenu.h>
#include <tqapplication.h>

#include <kcompletionbox.h>
#include <kcursor.h>
#include <kiconloader.h>
#include <kicontheme.h>
#include <klistviewsearchline.h>
#include <klineedit.h>
#include <klocale.h>
#include <knotifyclient.h>
#include <kpixmapprovider.h>
#include <kstdaccel.h>
#include <kurl.h>
#include <kurldrag.h>

#include <kdebug.h>

#include "kcombobox.h"

#include <stdlib.h> // getenv

class KComboBox::KComboBoxPrivate
{
public:
    KComboBoxPrivate() : klineEdit(0L)
    {
    }
    ~KComboBoxPrivate()
    {
    }

    KLineEdit *klineEdit;
};

KComboBox::KComboBox( TQWidget *parent, const char *name )
    : TQComboBox( parent, name ), d(new KComboBoxPrivate)
{
    init();
}

KComboBox::KComboBox( bool rw, TQWidget *parent, const char *name )
    : TQComboBox( rw, parent, name ), d(new KComboBoxPrivate)
{
    init();

    if ( rw )
    {
      KLineEdit *edit = new KLineEdit( this, "combo lineedit" );
      setLineEdit( edit );
    }
}

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

void KComboBox::init()
{
    // Permanently set some parameters in the parent object.
    TQComboBox::setAutoCompletion( false );

    // Enable context menu by default if widget
    // is editable.
    setContextMenuEnabled( true );
}


bool KComboBox::contains( const TQString& _text ) const
{
    if ( _text.isEmpty() )
        return false;

    const int itemCount = count();
    for (int i = 0; i < itemCount; ++i )
    {
        if ( text(i) == _text )
            return true;
    }
    return false;
}

void KComboBox::setAutoCompletion( bool autocomplete )
{
    if ( d->klineEdit )
    {
        if ( autocomplete )
        {
            d->klineEdit->setCompletionMode( KGlobalSettings::CompletionAuto );
            setCompletionMode( KGlobalSettings::CompletionAuto );
        }
        else
        {
            d->klineEdit->setCompletionMode( KGlobalSettings::completionMode() );
            setCompletionMode( KGlobalSettings::completionMode() );
        }
    }
}

void KComboBox::setContextMenuEnabled( bool showMenu )
{
    if( d->klineEdit )
        d->klineEdit->setContextMenuEnabled( showMenu );
}


void KComboBox::setURLDropsEnabled( bool enable )
{
    if ( d->klineEdit )
        d->klineEdit->setURLDropsEnabled( enable );
}

bool KComboBox::isURLDropsEnabled() const
{
    return d->klineEdit && d->klineEdit->isURLDropsEnabled();
}


void KComboBox::setCompletedText( const TQString& text, bool marked )
{
    if ( d->klineEdit )
        d->klineEdit->setCompletedText( text, marked );
}

void KComboBox::setCompletedText( const TQString& text )
{
    if ( d->klineEdit )
        d->klineEdit->setCompletedText( text );
}

void KComboBox::makeCompletion( const TQString& text )
{
    if( d->klineEdit )
        d->klineEdit->makeCompletion( text );

    else // read-only combo completion
    {
        if( text.isNull() || !listBox() )
            return;

        const int index = listBox()->index( listBox()->findItem( text ) );
        if( index >= 0 )
            setCurrentItem( index );
    }
}

void KComboBox::rotateText( KCompletionBase::KeyBindingType type )
{
    if ( d->klineEdit )
        d->klineEdit->rotateText( type );
}

// not needed anymore
bool KComboBox::eventFilter( TQObject* o, TQEvent* ev )
{
    return TQComboBox::eventFilter( o, ev );
}

void KComboBox::setTrapReturnKey( bool grab )
{
    if ( d->klineEdit )
        d->klineEdit->setTrapReturnKey( grab );
    else
        qWarning("KComboBox::setTrapReturnKey not supported with a non-KLineEdit.");
}

bool KComboBox::trapReturnKey() const
{
    return d->klineEdit && d->klineEdit->trapReturnKey();
}


void KComboBox::setEditURL( const KURL& url )
{
    TQComboBox::setEditText( url.prettyURL() );
}

void KComboBox::insertURL( const KURL& url, int index )
{
    TQComboBox::insertItem( url.prettyURL(), index );
}

void KComboBox::insertURL( const TQPixmap& pixmap, const KURL& url, int index )
{
    TQComboBox::insertItem( pixmap, url.prettyURL(), index );
}

void KComboBox::changeURL( const KURL& url, int index )
{
    TQComboBox::changeItem( url.prettyURL(), index );
}

void KComboBox::changeURL( const TQPixmap& pixmap, const KURL& url, int index )
{
    TQComboBox::changeItem( pixmap, url.prettyURL(), index );
}

void KComboBox::setCompletedItems( const TQStringList& items )
{
    if ( d->klineEdit )
        d->klineEdit->setCompletedItems( items );
}

KCompletionBox * KComboBox::completionBox( bool create )
{
    if ( d->klineEdit )
        return d->klineEdit->completionBox( create );
    return 0;
}

// TQWidget::create() turns off mouse-Tracking which would break auto-hiding
void KComboBox::create( WId id, bool initializeWindow, bool destroyOldWindow )
{
    TQComboBox::create( id, initializeWindow, destroyOldWindow );
    KCursor::setAutoHideCursor( lineEdit(), true, true );
}

void KComboBox::wheelEvent( TQWheelEvent *ev )
{
    // Not necessary anymore
    TQComboBox::wheelEvent( ev );
}

void KComboBox::setLineEdit( TQLineEdit *edit )
{
    if ( !editable() && edit &&
         !qstrcmp( edit->className(), "TQLineEdit" ) )
    {
        // uic generates code that creates a read-only KComboBox and then
        // calls combo->setEditable( true ), which causes TQComboBox to set up
        // a dumb TQLineEdit instead of our nice KLineEdit.
        // As some KComboBox features rely on the KLineEdit, we reject
        // this order here.
        delete edit;
        edit = new KLineEdit( this, "combo edit" );
    }

    TQComboBox::setLineEdit( edit );
    d->klineEdit = dynamic_cast<KLineEdit*>( edit );
    setDelegate( d->klineEdit );

    // Connect the returnPressed signal for both Q[K]LineEdits'
    if (edit)
        connect( edit, TQT_SIGNAL( returnPressed() ), TQT_SIGNAL( returnPressed() ));

    if ( d->klineEdit )
    {
        // someone calling KComboBox::setEditable( false ) destroys our
        // lineedit without us noticing. And KCompletionBase::delegate would
        // be a dangling pointer then, so prevent that. Note: only do this
        // when it is a KLineEdit!
        connect( edit, TQT_SIGNAL( destroyed() ), TQT_SLOT( lineEditDeleted() ));

        connect( d->klineEdit, TQT_SIGNAL( returnPressed( const TQString& )),
                 TQT_SIGNAL( returnPressed( const TQString& ) ));

        connect( d->klineEdit, TQT_SIGNAL( completion( const TQString& )),
                 TQT_SIGNAL( completion( const TQString& )) );

        connect( d->klineEdit, TQT_SIGNAL( substringCompletion( const TQString& )),
                 TQT_SIGNAL( substringCompletion( const TQString& )) );

        connect( d->klineEdit,
                 TQT_SIGNAL( textRotation( KCompletionBase::KeyBindingType )),
                 TQT_SIGNAL( textRotation( KCompletionBase::KeyBindingType )) );

        connect( d->klineEdit,
                 TQT_SIGNAL( completionModeChanged( KGlobalSettings::Completion )),
                 TQT_SIGNAL( completionModeChanged( KGlobalSettings::Completion)));

        connect( d->klineEdit,
                 TQT_SIGNAL( aboutToShowContextMenu( TQPopupMenu * )),
                 TQT_SIGNAL( aboutToShowContextMenu( TQPopupMenu * )) );

        connect( d->klineEdit,
                 TQT_SIGNAL( completionBoxActivated( const TQString& )),
                 TQT_SIGNAL( activated( const TQString& )) );
    }
}

void KComboBox::setCurrentItem( const TQString& item, bool insert, int index )
{
    int sel = -1;

    const int itemCount = count();
    for (int i = 0; i < itemCount; ++i)
    {
        if (text(i) == item)
        {
            sel = i;
            break;
        }
    }

    if (sel == -1 && insert)
    {
        insertItem(item, index);
        if (index >= 0)
            sel = index;
        else
            sel = count() - 1;
    }
    setCurrentItem(sel);
}

void KComboBox::lineEditDeleted()
{
    // yes, we need those ugly casts due to the multiple inheritance
    // sender() is guaranteed to be a KLineEdit (see the connect() to the
    // destroyed() signal
    const KCompletionBase *base = static_cast<const KCompletionBase*>( static_cast<const KLineEdit*>( sender() ));

    // is it our delegate, that is destroyed?
    if ( base == delegate() )
        setDelegate( 0L );
}


// *********************************************************************
// *********************************************************************

class KHistoryCombo::KHistoryComboPrivate
{
public:
    KHistoryComboPrivate() : bHistoryEditorEnabled(false)
    {
    }
    ~KHistoryComboPrivate()
    {
    }

    bool bHistoryEditorEnabled;
};

// we are always read-write
KHistoryCombo::KHistoryCombo( TQWidget *parent, const char *name )
    : KComboBox( true, parent, name ), d(new KHistoryComboPrivate)
{
    init( true ); // using completion
}

// we are always read-write
KHistoryCombo::KHistoryCombo( bool useCompletion,
                              TQWidget *parent, const char *name )
    : KComboBox( true, parent, name ), d(new KHistoryComboPrivate)
{
    init( useCompletion );
}

void KHistoryCombo::init( bool useCompletion )
{
    // Set a default history size to something reasonable, Qt sets it to INT_MAX by default
    setMaxCount( 50 );

    if ( useCompletion )
        completionObject()->setOrder( KCompletion::Weighted );

    setInsertionPolicy( NoInsertion );
    myIterateIndex = -1;
    myRotated = false;
    myPixProvider = 0L;

    // obey HISTCONTROL setting
    TQCString histControl = getenv("HISTCONTROL");
    if ( histControl == "ignoredups" || histControl == "ignoreboth" )
        setDuplicatesEnabled( false );

    connect( this, TQT_SIGNAL(aboutToShowContextMenu(TQPopupMenu*)),
             TQT_SLOT(addContextMenuItems(TQPopupMenu*)) );
    connect( this, TQT_SIGNAL( activated(int) ), TQT_SLOT( slotReset() ));
    connect( this, TQT_SIGNAL( returnPressed(const TQString&) ), TQT_SLOT(slotReset()));
}

KHistoryCombo::~KHistoryCombo()
{
    delete myPixProvider;
}

void KHistoryCombo::setHistoryItems( TQStringList items,
                                     bool setCompletionList )
{
    KComboBox::clear();

    // limit to maxCount()
    const int itemCount = items.count(); 
    const int toRemove = itemCount - maxCount();

    if (toRemove >= itemCount) {
        items.clear();
    } else {
        for (int i = 0; i < toRemove; ++i) 
            items.pop_front();
    }

    insertItems( items );

    if ( setCompletionList && useCompletion() ) {
        // we don't have any weighting information here ;(
        KCompletion *comp = completionObject();
        comp->setOrder( KCompletion::Insertion );
        comp->setItems( items );
        comp->setOrder( KCompletion::Weighted );
    }

    clearEdit();
}

TQStringList KHistoryCombo::historyItems() const
{
    TQStringList list;
    const int itemCount = count();
    for ( int i = 0; i < itemCount; ++i )
        list.append( text( i ) );

    return list;
}

void KHistoryCombo::clearHistory()
{
    const TQString temp = currentText();
    KComboBox::clear();
    if ( useCompletion() )
        completionObject()->clear();
    setEditText( temp );
}

void KHistoryCombo::addContextMenuItems( TQPopupMenu* menu )
{
    if ( menu )
    {
        menu->insertSeparator();
        if (d->bHistoryEditorEnabled) {
           int idedit = menu->insertItem( SmallIconSet("edit"), i18n("&Edit History..."), this, TQT_SLOT( slotEdit()) );
           menu->setItemEnabled(idedit, count());
        }
        int id = menu->insertItem( SmallIconSet("history_clear"), i18n("Clear &History"), this, TQT_SLOT( slotClear()));
        if (!count())
           menu->setItemEnabled(id, false);
    }
}

void KHistoryCombo::addToHistory( const TQString& item )
{
    if ( item.isEmpty() || (count() > 0 && item == text(0) )) {
        return;
    }

    bool wasCurrent = false;
    // remove all existing items before adding
    if ( !duplicatesEnabled() ) {
        int i = 0;
        int itemCount = count();
        while ( i < itemCount ) {
            if ( text( i ) == item ) {
                if ( !wasCurrent )
                  wasCurrent = ( i == currentItem() );
                removeItem( i );
                --itemCount;
            } else {
                ++i;
            }
        }
    }

    // now add the item
    if ( myPixProvider )
        insertItem( myPixProvider->pixmapFor(item, KIcon::SizeSmall), item, 0);
    else
        insertItem( item, 0 );

    if ( wasCurrent )
        setCurrentItem( 0 );

    const bool useComp = useCompletion();

    const int last = count() - 1; // last valid index
    const int mc = maxCount();
    const int stopAt = QMAX(mc, 0);

    for (int rmIndex = last; rmIndex >= stopAt; --rmIndex) {
        // remove the last item, as long as we are longer than maxCount()
        // remove the removed item from the completionObject if it isn't
        // anymore available at all in the combobox.
        const TQString rmItem = text( rmIndex );
        removeItem( rmIndex );
        if ( useComp && !contains( rmItem ) )
            completionObject()->removeItem( rmItem );
    }

    if ( useComp )
        completionObject()->addItem( item );
}

bool KHistoryCombo::removeFromHistory( const TQString& item )
{
    if ( item.isEmpty() )
        return false;

    bool removed = false;
    const TQString temp = currentText();
    int i = 0;
    int itemCount = count();
    while ( i < itemCount ) {
        if ( item == text( i ) ) {
            removed = true;
            removeItem( i );
            --itemCount;
        } else {
            ++i;
        }
    }

    if ( removed && useCompletion() )
        completionObject()->removeItem( item );

    setEditText( temp );
    return removed;
}

void KHistoryCombo::rotateUp()
{
    // save the current text in the lineedit
    if ( myIterateIndex == -1 )
        myText = currentText();

    ++myIterateIndex;

    // skip duplicates/empty items
    const int last = count() - 1; // last valid index
    const TQString currText = currentText();

    while ( myIterateIndex < last &&
            (currText == text( myIterateIndex ) ||
             text( myIterateIndex ).isEmpty()) )
        ++myIterateIndex;

    if ( myIterateIndex >= count() ) {
        myRotated = true;
        myIterateIndex = -1;

        // if the typed text is the same as the first item, skip the first
        if ( count() > 0 && myText == text(0) )
            myIterateIndex = 0;

        setEditText( myText );
    }
    else
        setEditText( text( myIterateIndex ));
}

void KHistoryCombo::rotateDown()
{
    // save the current text in the lineedit
    if ( myIterateIndex == -1 )
        myText = currentText();

    --myIterateIndex;

    const TQString currText = currentText();
    // skip duplicates/empty items
    while ( myIterateIndex >= 0 &&
            (currText == text( myIterateIndex ) ||
             text( myIterateIndex ).isEmpty()) )
        --myIterateIndex;


    if ( myIterateIndex < 0 ) {
        if ( myRotated && myIterateIndex == -2 ) {
            myRotated = false;
            myIterateIndex = count() - 1;
            setEditText( text(myIterateIndex) );
        }
        else { // bottom of history
            if ( myIterateIndex == -2 ) {
                KNotifyClient::event( (int)winId(), KNotifyClient::notification,
                                      i18n("No further item in the history."));
            }

            myIterateIndex = -1;
            if ( currentText() != myText )
                setEditText( myText );
        }
    }
    else
        setEditText( text( myIterateIndex ));

}

void KHistoryCombo::keyPressEvent( TQKeyEvent *e )
{
    KKey event_key( e );

    // going up in the history, rotating when reaching TQListBox::count()
    if ( KStdAccel::rotateUp().contains(event_key) )
        rotateUp();

    // going down in the history, no rotation possible. Last item will be
    // the text that was in the lineedit before Up was called.
    else if ( KStdAccel::rotateDown().contains(event_key) )
        rotateDown();
    else
        KComboBox::keyPressEvent( e );
}

void KHistoryCombo::wheelEvent( TQWheelEvent *ev )
{
    // Pass to poppable listbox if it's up
    TQListBox* const lb = listBox();
    if ( lb && lb->isVisible() )
    {
        TQApplication::sendEvent( lb, ev );
        return;
    }
    // Otherwise make it change the text without emitting activated
    if ( ev->delta() > 0 ) {
        rotateUp();
    } else {
        rotateDown();
    }
    ev->accept();
}

void KHistoryCombo::slotReset()
{
    myIterateIndex = -1;
    myRotated = false;
}


void KHistoryCombo::setPixmapProvider( KPixmapProvider *prov )
{
    if ( myPixProvider == prov )
        return;

    delete myPixProvider;
    myPixProvider = prov;

    // re-insert all the items with/without pixmap
    // I would prefer to use changeItem(), but that doesn't honor the pixmap
    // when using an editable combobox (what we do)
    if ( count() > 0 ) {
        TQStringList items( historyItems() );
        clear();
        insertItems( items );
    }
}

void KHistoryCombo::insertItems( const TQStringList& items )
{
    TQStringList::ConstIterator it = items.constBegin();
    const TQStringList::ConstIterator itEnd = items.constEnd();

    while ( it != itEnd ) {
        const TQString item = *it;
        if ( !item.isEmpty() ) { // only insert non-empty items
            if ( myPixProvider )
                insertItem( myPixProvider->pixmapFor(item, KIcon::SizeSmall),
                            item );
            else
                insertItem( item );
        }
        ++it;
    }
}

void KHistoryCombo::slotClear()
{
    clearHistory();
    emit cleared();
}

void KHistoryCombo::slotEdit()
{
    KHistoryComboEditor dlg( historyItems(), this );
    connect( &dlg, TQT_SIGNAL( removeFromHistory(const TQString&) ), TQT_SLOT( slotRemoveFromHistory(const TQString&)) );
    dlg.exec();
}

void KHistoryCombo::slotRemoveFromHistory(const TQString &entry)
{
    removeFromHistory(entry);
    emit removed(entry);
}

void KHistoryCombo::setHistoryEditorEnabled( bool enable )
{
    d->bHistoryEditorEnabled = enable;
}

bool KHistoryCombo::isHistoryEditorEnabled() const
{
    return d->bHistoryEditorEnabled;
}

void KComboBox::virtual_hook( int id, void* data )
{ KCompletionBase::virtual_hook( id, data ); }

void KHistoryCombo::virtual_hook( int id, void* data )
{ KComboBox::virtual_hook( id, data ); }

void KHistoryComboEditor::virtual_hook( int id, void* data )
{ KDialogBase::virtual_hook( id, data ); }

KHistoryComboEditor::KHistoryComboEditor( const TQStringList& entries, TQWidget *parent )
: KDialogBase( parent, "khistorycomboeditor", true, i18n( "History Editor" ),
    KDialogBase::Close | KDialogBase::User1, KDialogBase::User1, true,
    KGuiItem( i18n( "&Delete Entry" ), "editdelete") ), d(0)
{
    TQVBox* box = new TQVBox( this );
    box->setSpacing( KDialog::spacingHint() );
    setMainWidget( box );

    new TQLabel( i18n( "This dialog allows you to delete unwanted history items." ), box );

    // Add searchline
    TQHBox* searchbox = new TQHBox( box );
    searchbox->setSpacing( KDialog::spacingHint() );

    TQToolButton *clearSearch = new TQToolButton(searchbox);
    clearSearch->setTextLabel(i18n("Clear Search"), true);
    clearSearch->setIconSet(SmallIconSet(TQApplication::reverseLayout() ? "clear_left" : "locationbar_erase"));
    TQLabel* slbl = new TQLabel(i18n("&Search:"), searchbox);
    KListViewSearchLine* listViewSearch = new KListViewSearchLine(searchbox);
    slbl->setBuddy(listViewSearch);
    connect(clearSearch, TQT_SIGNAL(pressed()), listViewSearch, TQT_SLOT(clear()));

    // Add ListView
    m_pListView = new KListView( box );
    listViewSearch->setListView(m_pListView);
    m_pListView->setAllColumnsShowFocus(true);
    m_pListView->header()->hide();
    m_pListView->addColumn("");
    m_pListView->setRenameable( 0 );

    box->setStretchFactor( m_pListView, 1 );

    TQStringList newlist = entries;
    for ( TQStringList::Iterator it = newlist.begin(); it != newlist.end(); ++it ) {
        new TQListViewItem( m_pListView, *it );
    }

    m_pListView->setMinimumSize( m_pListView->sizeHint() );

    connect( m_pListView, TQT_SIGNAL( selectionChanged( TQListViewItem * ) ),
             this, TQT_SLOT( slotSelectionChanged( TQListViewItem * ) ) );

    enableButton( KDialogBase::User1, false );

    resize( sizeHint() );
}

KHistoryComboEditor::~KHistoryComboEditor()
{
}

void KHistoryComboEditor::slotUser1() // Delete button
{
    TQListViewItem *item = m_pListView->selectedItem();

    if ( item ) {
       emit removeFromHistory( item->text(0) );
       m_pListView->takeItem( item );
       enableButton( KDialogBase::User1, false );
    }
}

void KHistoryComboEditor::slotSelectionChanged( TQListViewItem * item )
{
    enableButton( KDialogBase::User1, item );
}

#include "kcombobox.moc"