/* This file is part of the KDE Display Manager Configuration package
    Copyright (C) 1997 Thomas Tanghus (tanghus@earthling.net)

    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 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 <unistd.h>
#include <sys/types.h>

#include <tqstyle.h>
#include <tqlayout.h>
#include <tqlabel.h>
#include <tqtooltip.h>
#include <tqvalidator.h>
#include <tqwhatsthis.h>
#include <tqvgroupbox.h>
#include <tqpushbutton.h>

#include <tdefiledialog.h>
#include <kimageio.h>
#include <kimagefilepreview.h>
#include <tdeio/netaccess.h>
#include <tdelocale.h>
#include <kstandarddirs.h>
#include <tdemessagebox.h>
#include <kurldrag.h>

#include "tdm-users.h"

#include <sys/stat.h>


extern KSimpleConfig *config;

TDMUsersWidget::TDMUsersWidget(TQWidget *parent, const char *name)
    : TQWidget(parent, name)
{
#ifdef __linux__
    struct stat st;
    if (!stat( "/etc/debian_version", &st )) {	/* debian */
	defminuid = "1000";
	defmaxuid = "29999";
    } else if (!stat( "/usr/portage", &st )) {	/* gentoo */
	defminuid = "1000";
	defmaxuid = "65000";
    } else if (!stat( "/etc/mandrake-release", &st )) {	/* mandrake - check before redhat! */
	defminuid = "500";
	defmaxuid = "65000";
    } else if (!stat( "/etc/redhat-release", &st )) {	/* redhat */
	defminuid = "100";
	defmaxuid = "65000";
    } else /* if (!stat( "/etc/SuSE-release", &st )) */ {	/* suse */
	defminuid = "500";
	defmaxuid = "65000";
    }
#else
    defminuid = "1000";
    defmaxuid = "65000";
#endif

    // We assume that $kde_datadir/tdm exists, but better check for pics/ and pics/users,
    // and create them if necessary.
    config->setGroup( "X-*-Greeter" );
    m_userPixDir = config->readEntry( "FaceDir", TDEGlobal::dirs()->resourceDirs("data").last() + "tdm/faces" ) + '/';
    m_notFirst = false;
    TQDir testDir( m_userPixDir );
    if ( !testDir.exists() && !testDir.mkdir( testDir.absPath() ) && !geteuid() )
        KMessageBox::sorry( this, i18n("Unable to create folder %1").arg( testDir.absPath() ) );
    chmod( TQFile::encodeName( m_userPixDir ), 0755 );

    m_defaultText = i18n("<default>");

    TQString wtstr;

    minGroup = new TQGroupBox( 2, Qt::Horizontal, i18n("System U&IDs"), this );
    TQWhatsThis::add( minGroup, i18n("Users with a UID (numerical user identification) outside this range will not be listed by TDM and this setup dialog."
      " Note that users with the UID 0 (typically root) are not affected by this and must be"
      " explicitly hidden in \"Not hidden\" mode."));
    TQSizePolicy sp_ign_fix( TQSizePolicy::Ignored, TQSizePolicy::Fixed );
    TQValidator *valid = new TQIntValidator( 0, 999999, TQT_TQOBJECT(minGroup) );
    TQLabel *minlab = new TQLabel( i18n("Below:"), minGroup );
    leminuid = new KLineEdit( minGroup );
    minlab->setBuddy( leminuid );
    leminuid->setSizePolicy( sp_ign_fix );
    leminuid->setValidator( valid );
    connect( leminuid, TQT_SIGNAL(textChanged( const TQString & )), TQT_SLOT(slotChanged()) );
    connect( leminuid, TQT_SIGNAL(textChanged( const TQString & )), TQT_SLOT(slotMinMaxChanged()) );
    TQLabel *maxlab = new TQLabel( i18n("Above:"), minGroup );
    lemaxuid = new KLineEdit( minGroup );
    maxlab->setBuddy( lemaxuid );
    lemaxuid->setSizePolicy( sp_ign_fix );
    lemaxuid->setValidator( valid );
    connect(lemaxuid, TQT_SIGNAL(textChanged( const TQString & )), TQT_SLOT(slotChanged()) );
    connect(lemaxuid, TQT_SIGNAL(textChanged( const TQString & )), TQT_SLOT(slotMinMaxChanged()) );

    usrGroup = new TQButtonGroup( 5, Qt::Vertical, i18n("Users"), this );
    connect( usrGroup, TQT_SIGNAL(clicked( int )), TQT_SLOT(slotShowOpts()) );
    connect( usrGroup, TQT_SIGNAL(clicked( int )), TQT_SLOT(slotChanged()) );
    cbshowlist = new TQCheckBox( i18n("Show list"), usrGroup );
    TQWhatsThis::add( cbshowlist, i18n("If this option is checked, TDM will show a list of users,"
      " so users can click on their name or image rather than typing in their login.") );
    cbcomplete = new TQCheckBox( i18n("Autocompletion"), usrGroup );
    TQWhatsThis::add( cbcomplete, i18n("If this option is checked, TDM will automatically complete"
      " user names while they are typed in the line edit.") );
    cbinverted = new TQCheckBox( i18n("Inverse selection"), usrGroup );
    TQWhatsThis::add( cbinverted, i18n("This option specifies how the users for \"Show list\" and \"Autocompletion\""
      " are selected in the \"Select users and groups\" list: "
      "If not checked, select only the checked users. "
      "If checked, select all non-system users, except the checked ones."));
    cbusrsrt = new TQCheckBox( i18n("Sor&t users"), usrGroup );
    connect( cbusrsrt, TQT_SIGNAL(toggled( bool )), TQT_SLOT(slotChanged()) );
    TQWhatsThis::add( cbusrsrt, i18n("If this is checked, TDM will alphabetically sort the user list."
      " Otherwise users are listed in the order they appear in the password file.") );

    wstack = new TQWidgetStack( this );
    s_label = new TQLabel( wstack, i18n("S&elect users and groups:"), this );
    optinlv = new TDEListView( this );
    optinlv->addColumn( i18n("Selected Users") );
    optinlv->setResizeMode( TQListView::LastColumn );
    TQWhatsThis::add( optinlv, i18n("TDM will show all checked users. Entries denoted with '@' are user groups. Checking a group is like checking all users in that group.") );
    wstack->addWidget( optinlv );
    connect( optinlv, TQT_SIGNAL(clicked( TQListViewItem * )),
	     TQT_SLOT(slotUpdateOptIn( TQListViewItem * )) );
    connect( optinlv, TQT_SIGNAL(clicked( TQListViewItem * )),
	     TQT_SLOT(slotChanged()) );
    optoutlv = new TDEListView( this );
    optoutlv->addColumn( i18n("Hidden Users") );
    optoutlv->setResizeMode( TQListView::LastColumn );
    TQWhatsThis::add( optoutlv, i18n("TDM will show all non-checked non-system users. Entries denoted with '@' are user groups. Checking a group is like checking all users in that group.") );
    wstack->addWidget( optoutlv );
    connect( optoutlv, TQT_SIGNAL(clicked( TQListViewItem * )),
	     TQT_SLOT(slotUpdateOptOut( TQListViewItem * )) );
    connect( optoutlv, TQT_SIGNAL(clicked( TQListViewItem * )),
	     TQT_SLOT(slotChanged()) );

    faceGroup = new TQButtonGroup( 5, Qt::Vertical, i18n("User Image Source"), this );
    TQWhatsThis::add( faceGroup, i18n("Here you can specify where TDM will obtain the images that represent users."
      " \"Admin\" represents the global folder; these are the pictures you can set below."
      " \"User\" means that TDM should read the user's $HOME/.face.icon file."
      " The two selections in the middle define the order of preference if both sources are available.") );
    connect( faceGroup, TQT_SIGNAL(clicked( int )), TQT_SLOT(slotFaceOpts()) );
    connect( faceGroup, TQT_SIGNAL(clicked( int )), TQT_SLOT(slotChanged()) );
    rbadmonly = new TQRadioButton( i18n("Admin"), faceGroup );
    rbprefadm = new TQRadioButton( i18n("Admin, user"), faceGroup );
    rbprefusr = new TQRadioButton( i18n("User, admin"), faceGroup );
    rbusronly = new TQRadioButton( i18n("User"), faceGroup );

    TQGroupBox *picGroup = new TQVGroupBox( i18n("User Images"), this );
    TQWidget *hlpw = new TQWidget( picGroup );
    usercombo = new KComboBox( hlpw );
    TQWhatsThis::add( usercombo, i18n("The user the image below belongs to.") );
    connect( usercombo, TQT_SIGNAL(activated( int )),
	     TQT_SLOT(slotUserSelected()) );
    TQLabel *userlabel = new TQLabel( usercombo, i18n("User:"), hlpw );
    userbutton = new TQPushButton( hlpw );
    userbutton->setAcceptDrops( true );
    userbutton->installEventFilter( this ); // for drag and drop
    uint sz = style().pixelMetric( TQStyle::PM_ButtonMargin ) * 2 + 48;
    userbutton->setFixedSize( sz, sz );
    connect( userbutton, TQT_SIGNAL(clicked()),
	     TQT_SLOT(slotUserButtonClicked()) );
    TQToolTip::add( userbutton, i18n("Click or drop an image here") );
    TQWhatsThis::add( userbutton, i18n("Here you can see the image assigned to the user selected in the combo box above. Click on the image button to select from a list"
      " of images or drag and drop your own image on to the button (e.g. from Konqueror).") );
    rstuserbutton = new TQPushButton( i18n("Unset"), hlpw );
    TQWhatsThis::add( rstuserbutton, i18n("Click this button to make TDM use the default image for the selected user.") );
    connect( rstuserbutton, TQT_SIGNAL(clicked()),
	     TQT_SLOT(slotUnsetUserPix()) );
    TQGridLayout *hlpl = new TQGridLayout( hlpw, 3, 2, 0, KDialog::spacingHint() );
    hlpl->addWidget( userlabel, 0, 0 );
//    hlpl->addSpacing( KDialog::spacingHint() );
    hlpl->addWidget( usercombo, 0, 1 );
    hlpl->addMultiCellWidget( userbutton, 1,1, 0,1, Qt::AlignHCenter );
    hlpl->addMultiCellWidget( rstuserbutton, 2,2, 0,1, Qt::AlignHCenter );

    TQHBoxLayout *main = new TQHBoxLayout( this, 10 );

    TQVBoxLayout *lLayout = new TQVBoxLayout( main, 10 );
    lLayout->addWidget( minGroup );
    lLayout->addWidget( usrGroup );
    lLayout->addStretch( 1 );

    TQVBoxLayout *mLayout = new TQVBoxLayout( main, 10 );
    mLayout->addWidget( s_label );
    mLayout->addWidget( wstack );
    mLayout->setStretchFactor( wstack, 1 );
    main->setStretchFactor( mLayout, 1 );

    TQVBoxLayout *rLayout = new TQVBoxLayout( main, 10 );
    rLayout->addWidget( faceGroup );
    rLayout->addWidget( picGroup );
    rLayout->addStretch( 1 );

}

void TDMUsersWidget::makeReadOnly()
{
    leminuid->setReadOnly(true);
    lemaxuid->setReadOnly(true);
    cbshowlist->setEnabled(false);
    cbcomplete->setEnabled(false);
    cbinverted->setEnabled(false);
    cbusrsrt->setEnabled(false);
    rbadmonly->setEnabled(false);
    rbprefadm->setEnabled(false);
    rbprefusr->setEnabled(false);
    rbusronly->setEnabled(false);
    wstack->setEnabled(false);
    disconnect( userbutton, TQT_SIGNAL(clicked()), this, TQT_SLOT(slotUserButtonClicked()) );
    userbutton->setAcceptDrops(false);
    rstuserbutton->setEnabled(false);
}

void TDMUsersWidget::slotShowOpts()
{
    bool en = cbshowlist->isChecked() || cbcomplete->isChecked();
    cbinverted->setEnabled( en );
    cbusrsrt->setEnabled( en );
    wstack->setEnabled( en );
    wstack->raiseWidget( cbinverted->isChecked() ? optoutlv : optinlv );
    en = cbshowlist->isChecked();
    faceGroup->setEnabled( en );
    if (!en) {
	usercombo->setEnabled( false );
	userbutton->setEnabled( false );
	rstuserbutton->setEnabled( false );
    } else
	slotFaceOpts();
}

void TDMUsersWidget::slotFaceOpts()
{
    bool en = !rbusronly->isChecked();
    usercombo->setEnabled( en );
    userbutton->setEnabled( en );
    if (en)
	slotUserSelected();
    else
	rstuserbutton->setEnabled( false );
}

void TDMUsersWidget::slotUserSelected()
{
    TQString user = usercombo->currentText();
    TQImage p;
    if (user != m_defaultText &&
	p.load( m_userPixDir + user + ".face.icon" )) {
	rstuserbutton->setEnabled( !getuid() );
    } else {
	p.load( m_userPixDir + ".default.face.icon" );
	rstuserbutton->setEnabled( false );
    }
    if (!p.isNull()) {
	    userbutton->setPixmap( p.smoothScale( 48, 48, TQ_ScaleMin ) );
    }
}


void TDMUsersWidget::changeUserPix(const TQString &pix)
{
    TQString user( usercombo->currentText() );
    if (user == m_defaultText)
    {
       user = ".default";
       if (KMessageBox::questionYesNo(this, i18n("Save image as default image?"),TQString::null,KStdGuiItem::save(),KStdGuiItem::cancel())
            != KMessageBox::Yes)
          return;
    }

    TQImage p( pix );
    if (p.isNull()) {
	KMessageBox::sorry( this,
			    i18n("There was an error loading the image\n"
				 "%1").arg( pix ) );
	return;
    }

    p = p.smoothScale( 48, 48, TQ_ScaleMin );
    TQString userpix = m_userPixDir + user + ".face.icon";
    if (!p.save( userpix, "PNG" ))
        KMessageBox::sorry(this,
	    i18n("There was an error saving the image:\n%1")
		.arg( userpix ) );
    else
        chmod( TQFile::encodeName( userpix ), 0644 );

    slotUserSelected();
}

void TDMUsersWidget::slotUserButtonClicked()
{
    KFileDialog dlg(m_notFirst ? TQString::null :
	TDEGlobal::dirs()->resourceDirs("data").last() + "tdm/pics/users",
                    KImageIO::pattern( KImageIO::Reading ),
                    this, 0, true);
    dlg.setOperationMode( KFileDialog::Opening );
    dlg.setCaption( i18n("Choose Image") );
    dlg.setMode( KFile::File | KFile::LocalOnly );

    KImageFilePreview *ip = new KImageFilePreview( &dlg );
    dlg.setPreviewWidget( ip );
    if (dlg.exec() != TQDialog::Accepted)
	return;
    m_notFirst = true;

    changeUserPix( dlg.selectedFile() );
}

void TDMUsersWidget::slotUnsetUserPix()
{
    TQFile::remove( m_userPixDir + usercombo->currentText() + ".face.icon" );
    slotUserSelected();
}

bool TDMUsersWidget::eventFilter(TQObject *, TQEvent *e)
{
    if (e->type() == TQEvent::DragEnter) {
	TQDragEnterEvent *ee = (TQDragEnterEvent *) e;
	ee->accept( KURLDrag::canDecode(ee) );
        return true;
    }

    if (e->type() == TQEvent::Drop) {
        userButtonDropEvent((TQDropEvent *) e);
        return true;
    }

    return false;
}

KURL *decodeImgDrop(TQDropEvent *e, TQWidget *wdg);

void TDMUsersWidget::userButtonDropEvent(TQDropEvent *e)
{
    KURL *url = decodeImgDrop(e, this);
    if (url) {
	TQString pixpath;
	TDEIO::NetAccess::download(*url, pixpath, parentWidget());
	changeUserPix( pixpath );
	TDEIO::NetAccess::removeTempFile(pixpath);
	delete url;
    }
}

void TDMUsersWidget::save()
{
    config->setGroup( "X-*-Greeter" );

    config->writeEntry( "MinShowUID", leminuid->text() );
    config->writeEntry( "MaxShowUID", lemaxuid->text() );

    config->writeEntry( "UserList", cbshowlist->isChecked() );
    config->writeEntry( "UserCompletion", cbcomplete->isChecked() );
    config->writeEntry( "ShowUsers",
	cbinverted->isChecked() ? "NotHidden" : "Selected" );
    config->writeEntry( "SortUsers", cbusrsrt->isChecked() );

    config->writeEntry( "HiddenUsers", hiddenUsers );
    config->writeEntry( "SelectedUsers", selectedUsers );

    config->writeEntry( "FaceSource",
	rbadmonly->isChecked() ? "AdminOnly" :
	rbprefadm->isChecked() ? "PreferAdmin" :
	rbprefusr->isChecked() ? "PreferUser" : "UserOnly" );
}


void TDMUsersWidget::updateOptList( TQListViewItem *item, TQStringList &list )
{
    if ( !item )
        return;
    TQCheckListItem *itm = (TQCheckListItem *)item;
    TQStringList::iterator it = list.find( itm->text() );
    if (itm->isOn()) {
	if (it == list.end())
	    list.append( itm->text() );
    } else {
	if (it != list.end())
	    list.remove( it );
    }
}

void TDMUsersWidget::slotUpdateOptIn( TQListViewItem *item )
{
    updateOptList( item, selectedUsers );
}

void TDMUsersWidget::slotUpdateOptOut( TQListViewItem *item )
{
    updateOptList( item, hiddenUsers );
}

void TDMUsersWidget::slotClearUsers()
{
    optinlv->clear();
    optoutlv->clear();
    usercombo->clear();
    usercombo->insertItem( m_defaultText );
}

void TDMUsersWidget::slotAddUsers(const TQMap<TQString,int> &users)
{
    TQMapConstIterator<TQString,int> it;
    for (it = users.begin(); it != users.end(); ++it) {
      const TQString *name = &it.key();
      (new TQCheckListItem(optinlv, *name, TQCheckListItem::CheckBox))->
	      setOn(selectedUsers.find(*name) != selectedUsers.end());
      (new TQCheckListItem(optoutlv, *name, TQCheckListItem::CheckBox))->
	      setOn(hiddenUsers.find(*name) != hiddenUsers.end());
      if ((*name)[0] != '@')
        usercombo->insertItem(*name);
    }
    optinlv->sort();
    optoutlv->sort();
    if (usercombo->listBox())
        usercombo->listBox()->sort();
}

void TDMUsersWidget::slotDelUsers(const TQMap<TQString,int> &users)
{
    TQMapConstIterator<TQString,int> it;
    for (it = users.begin(); it != users.end(); ++it) {
        const TQString *name = &it.key();
        if (usercombo->listBox())
            delete usercombo->listBox()->findItem( *name, ExactMatch | CaseSensitive );
        delete optinlv->findItem( *name, 0 );
        delete optoutlv->findItem( *name, 0 );
    }
}

void TDMUsersWidget::load()
{
    TQString str;

    config->setGroup("X-*-Greeter");

    selectedUsers = config->readListEntry( "SelectedUsers");
    hiddenUsers = config->readListEntry( "HiddenUsers");

    leminuid->setText(config->readEntry("MinShowUID", defminuid));
    lemaxuid->setText(config->readEntry("MaxShowUID", defmaxuid));

    cbshowlist->setChecked( config->readBoolEntry( "UserList", true ) );
    cbcomplete->setChecked( config->readBoolEntry( "UserCompletion", false ) );
    cbinverted->setChecked( config->readEntry( "ShowUsers" ) != "Selected" );
    cbusrsrt->setChecked(config->readBoolEntry("SortUsers", true));

    TQString ps = config->readEntry( "FaceSource" );
    if (ps == TQString::fromLatin1("UserOnly"))
	rbusronly->setChecked(true);
    else if (ps == TQString::fromLatin1("PreferUser"))
	rbprefusr->setChecked(true);
    else if (ps == TQString::fromLatin1("PreferAdmin"))
	rbprefadm->setChecked(true);
    else
	rbadmonly->setChecked(true);

    slotUserSelected();

    slotShowOpts();
    slotFaceOpts();
}

void TDMUsersWidget::defaults()
{
    leminuid->setText( defminuid );
    lemaxuid->setText( defmaxuid );
    cbshowlist->setChecked( true );
    cbcomplete->setChecked( false );
    cbinverted->setChecked( true );
    cbusrsrt->setChecked( true );
    rbadmonly->setChecked( true );
    hiddenUsers.clear();
    selectedUsers.clear();
    slotShowOpts();
    slotFaceOpts();
}

void TDMUsersWidget::slotMinMaxChanged()
{
    emit setMinMaxUID( leminuid->text().toInt(), lemaxuid->text().toInt() );
}

void TDMUsersWidget::slotChanged()
{
  emit changed(true);
}

#include "tdm-users.moc"
