/* This file is part of the KDE project
   Copyright (C) 1998, 1999 Torben Weis <weis@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., 59 Temple Place - Suite 330,
   Boston, MA 02111-1307, USA.
*/


#include <assert.h>
#include <string.h>
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>
#include <kglobalsettings.h>

#include <qkeycode.h>
#include <qpalette.h>
#include <qdragobject.h>
#include <qevent.h>
#include <qrect.h>
#include <qdir.h>

#include <dcopclient.h>
#include <kapp.h>
#include <kcursor.h>
#include <kdebug.h>
#include <konqdirlister.h>
#include <kstddirs.h>
#include <kfileivi.h>
#include <klocale.h>
#include <kio/job.h>
#include <kio/paste.h>
#include <knewmenu.h>
#include <konqdefaults.h>
#include <konqdrag.h>
#include <konqpopupmenu.h>
#include <konqsettings.h>
#include <konqoperations.h>
#include <kstdaccel.h>
#include <ksycoca.h>
#include <kurl.h>
#include <kwm.h>
#include <kmessagebox.h>
#include <kglobalaccel.h>
#include <kwinmodule.h>
#include <krun.h>
#include <kdesktopfile.h>

#include <kwm.h>
// root window hack
#include <X11/X.h>
#include <X11/Xos.h>
#include <X11/Xlib.h>
#include <X11/Xatom.h>
// ----

#include "desktop.h"
#include "krootwm.h"
#include "bgmanager.h"

bool KDesktop::s_bMoveSelection = false;

// -----------------------------------------------------------------------------

KDesktop::KDesktop( const QString& _url, bool x_root_hack, bool auto_start, bool wait_for_kded ) :
    KonqIconViewWidget( 0L, "desktop", x_root_hack ? (WStyle_Customize | WStyle_NoBorder) : 0 ),
    // those two WStyle_ break kdesktop when the root-hack isn't used (no Dnd)

    DCOPObject( "KDesktopIface" )
{
  m_bAutoStart = auto_start;
  m_bWaitForKded = wait_for_kded;
  KGlobal::locale()->insertCatalogue("kdesktop");
  KGlobal::locale()->insertCatalogue("libkonq"); // needed for apps using libkonq

  setCaption( "THE DESKTOP"); // kwin will understand that for now

  m_lastIcon = 0;
  m_hasExistingPos = false;
  setFrameStyle(NoFrame);
  setAcceptDrops(true); // WStyle_Customize seems to disable that
  // we don't want a scrolling desktop
  setVScrollBarMode( AlwaysOff );
  setHScrollBarMode( AlwaysOff );
  setDragAutoScroll( false );
  m_pKwinmodule = new KWinModule( this );

  KURL url( _url );
  assert( !url.isMalformed() );
  setURL( url );

  // Dont repaint on configuration changes during construction
  m_bInit = true;

  setFocusPolicy( StrongFocus );
  viewport()->setFocusPolicy( StrongFocus );

  if ( x_root_hack )
  {
    // this is a ugly hack to make Dnd work
    // Matthias told me that it won't be necessary with kwin
    // actually my first try with ICCCM (Dirk) :-)
    unsigned long data[2];
    data[0] = (unsigned long) 1;
    data[1] = (unsigned long) None;
    Atom wm_state = XInternAtom(qt_xdisplay(), "WM_STATE", False);
    XChangeProperty(qt_xdisplay(), winId(), wm_state, wm_state, 32,
                    PropModeReplace, (unsigned char *)data, 2);

    QRect area = KWM::windowRegion(KWM::currentDesktop());
    setGeometry( area );
  }
  else
    setGeometry( QApplication::desktop()->geometry() );

  // Geert Jansen: backgroundmanager belongs here
  KBackgroundManager *bgMgr = new KBackgroundManager(viewport(), m_pKwinmodule );
  connect( bgMgr, SIGNAL( initDone()), SLOT( backgroundInitDone()));

  lower();

  splash = 0;

  connect( this, SIGNAL( executed( QIconViewItem * ) ),
           SLOT( slotReturnPressed( QIconViewItem * ) ) );
  connect( this, SIGNAL( returnPressed( QIconViewItem * ) ),
           SLOT( slotReturnPressed( QIconViewItem * ) ) );
  connect( this, SIGNAL( enableAction( const char * , bool ) ),
           SLOT( slotEnableAction( const char * , bool ) ) );

  connect( this, SIGNAL( mouseButtonPressed(int, QIconViewItem*, const QPoint&)),
           SLOT( slotMouseButtonPressed(int, QIconViewItem*, const QPoint&)) );
  connect( this, SIGNAL(itemRenamed(QIconViewItem*)),
           SLOT( slotItemRenamed(QIconViewItem*)) );
  connect( this, SIGNAL( dropped( QDropEvent *, const QValueList<QIconDragItem> & ) ),
  	   this, SLOT( slotSaveDropPosition( QDropEvent *, const QValueList<QIconDragItem> & ) ) );

  connect(KSycoca::self(), SIGNAL(databaseChanged()), this, SLOT(slotDatabaseChanged()));

  if (!m_bWaitForKded)
     QTimer::singleShot(100, this, SLOT( slotStart()));

  m_dotDirectory = 0;

  connect( kapp, SIGNAL( shutDown() ),
	   this, SLOT( slotSaveIconPositions() ) );
}

void
KDesktop::backgroundInitDone()
{
  show();
  if (m_bWaitForKded)
  {
     splash = new QFrame(0, "intro_splash",
	WStyle_Customize | WStyle_StaysOnTop | WStyle_NoBorder
			 | WDestructiveClose );
     splash->setFrameStyle( QFrame::Box | QFrame::Raised );
     splash->setLineWidth(2);
     KWM::setDecoration( splash->winId(), KWM::noDecoration );
     QPixmap pm( locate("data", "kdesktop/pics/splash.png"));
     splash->setBackgroundPixmap(pm);
     int x = (QApplication::desktop()->width()- pm.width())/2;
     int y = (QApplication::desktop()->height()- pm.height())/2;
     splash->move(x,y);
     splash->setFixedSize( pm.size());
     splash->show();
     if (!m_bInit)
        QTimer::singleShot(3000, this, SLOT( slotSplashDone()));
  }
}

void
KDesktop::slotSplashDone()
{
  delete splash;
  splash = 0;
}

void
KDesktop::slotStart()
{
  if (!m_bInit) return;

  initConfig();

  // Now we may react to configuration changes
  m_bInit = false;

  // Create the directory lister
  m_dirLister = new KonqDirLister();

  connect( m_dirLister, SIGNAL( clear() ), this, SLOT( slotClear() ) );
  connect( m_dirLister, SIGNAL( started(const QString&) ), this, SLOT( slotStarted() ) );
  connect( m_dirLister, SIGNAL( completed() ), this, SLOT( slotCompleted() ) );
  connect( m_dirLister, SIGNAL( newItems( const KFileItemList & ) ),
           this, SLOT( slotNewItems( const KFileItemList & ) ) );
  connect( m_dirLister, SIGNAL( deleteItem( KFileItem * ) ),
           this, SLOT( slotDeleteItem( KFileItem * ) ) );
  connect( m_dirLister, SIGNAL( refreshItems( const KFileItemList & ) ),
           this, SLOT( slotRefreshItems( const KFileItemList & ) ) );

  // Start the directory lister !
  m_dirLister->openURL( m_url, m_bShowDot );

  setIcons( 0 ); // to be made configurable ?
  setResizeMode( Fixed );

    // Global keys
  m_keys = new KGlobalAccel;
  m_keys->insertItem(i18n("Execute command"),  "Execute command", "ALT+F2");
  m_keys->connectItem("Execute command", this, SLOT(slotExecuteCommand()));

  m_keys->readSettings();

  (void) new KAction( i18n( "&Cut" ), "editcut", KStdAccel::key(KStdAccel::Cut), this, SLOT( slotCut() ), &m_actionCollection, "cut" );
  (void) new KAction( i18n( "&Copy" ), "editcopy", KStdAccel::key(KStdAccel::Copy), this, SLOT( slotCopy() ), &m_actionCollection, "copy" );
  (void) new KAction( i18n( "&Paste" ), "editpaste", KStdAccel::key(KStdAccel::Paste), this, SLOT( slotPaste() ), &m_actionCollection, "paste" );

  KConfig config("konquerorrc", false, true); // This is set in konquerorrc...
  config.setGroup( "Trash" );
  int deleteAction = config.readNumEntry("DeleteAction", DEFAULT_DELETEACTION);
  const int deleteKey = CTRL+Key_Delete ; // Key_Delete conflict with the location bar
  (void) new KAction( i18n( "&Move to Trash" ), "trash", deleteAction==1 ? deleteKey : 0, this, SLOT( slotTrash() ), &m_actionCollection, "trash" );
  (void) new KAction( i18n( "&Delete" ), deleteAction==2 ? deleteKey : 0, this, SLOT( slotDelete() ), &m_actionCollection, "del" );
  (void) new KAction( i18n( "&Shred" ), deleteAction==3 ? deleteKey : 0, this, SLOT( slotShred() ), &m_actionCollection, "shred" );

  if (splash)
     QTimer::singleShot(3000, this, SLOT( slotSplashDone()));

  new KRootWm( this ); // handler for root menu (used by kdesktop on RMB click)

  if ( m_bAutoStart )
  {
     // now let's execute all the stuff in the autostart folder.
     // the stuff will actually be really executed when the event loop is
     // entered, since KRun internally uses a QTimer
     QDir dir( KGlobalSettings::autostartPath() );
     QStringList entries = dir.entryList( QDir::Files );
     QStringList::Iterator it = entries.begin();
     QStringList::Iterator end = entries.end();
     for (; it != end; ++it )
     {
            // Don't execute backup files
            if ( (*it).right(1) != "~" && (*it).right(4) != ".bak" &&
                 ( (*it)[0] != '%' || (*it).right(1) != "%" ) &&
                 ( (*it)[0] != '#' || (*it).right(1) != "#" ) )
                (void) new KRun( (*it).prepend( QString::fromLatin1( "file:" ) + dir.absPath() + '/' ), 0, true );
     }
   }

  connect(kapp, SIGNAL(kdisplayFontChanged()), SLOT(slotConfigure()));
}

// -----------------------------------------------------------------------------

KDesktop::~KDesktop()
{
  delete m_dirLister;
}

// -----------------------------------------------------------------------------

void KDesktop::initConfig()
{
  KConfig * config = KGlobal::config();
  config->setGroup( "Desktop Icons" );
  m_bShowDot = config->readBoolEntry("ShowHidden", DEFAULT_SHOW_HIDDEN_ROOT_ICONS);
  m_bVertAlign = config->readBoolEntry("VertAlign", DEFAULT_VERT_ALIGN);
  if ( !m_bInit ) { // only when called while running - not on first startup
    m_dirLister->setShowingDotFiles( m_bShowDot );
    m_keys->readSettings();
  }
  m_tAlign = m_bVertAlign ? TopToBottom : LeftToRight;
  setArrangement(m_tAlign);

  KonqIconViewWidget::initConfig();

  setAutoArrange( false );
}

// -----------------------------------------------------------------------------

void KDesktop::slotExecuteCommand()
{
    // this function needs to be duplicated since it appears that one
    // cannot have a 'slot' be a DCOP method.  if this changes in the
    // future, then 'slotExecuteCommand' and 'popupExecuteCommand' can
    // merge into one slot.
    popupExecuteCommand();
}

// -----------------------------------------------------------------------------

void KDesktop::popupExecuteCommand()
{
    if (m_bInit) return;
    KRootWm::getRootWm()->slotExec();
}

// -----------------------------------------------------------------------------

void KDesktop::rearrangeIcons( int bAsk )
{
  if ( (bool)bAsk )
    if ( KMessageBox::questionYesNo( 0L,
         i18n( "Do you really want to rearrange your icons?" )) == KMessageBox::No )
      return;

  arrangeItemsInGrid();
  slotSaveIconPositions();
}

// -----------------------------------------------------------------------------

void KDesktop::lineupIcons()
{
  KonqIconViewWidget::lineupIcons();
  slotSaveIconPositions();
}

// -----------------------------------------------------------------------------

QStringList KDesktop::selectedURLs()
{
    QStringList seq;

    QIconViewItem *it = firstItem();
    for (; it; it = it->nextItem() )
        if ( it->isSelected() ) {
            KFileItem *fItem = ((KFileIVI *)it)->item();
            seq.append( fItem->url().url() ); // copy the URL
        }

    return seq;
}

// -----------------------------------------------------------------------------

void KDesktop::slotConfigure()
{
   configure();
}

void KDesktop::configure()
{
    // re-read configuration and apply it
    KGlobal::config()->reparseConfiguration();
    KonqFMSettings::reparseConfiguration();
    if (!m_bInit)
    {
       initConfig();
       KRootWm::getRootWm()->initConfig();
    }
}


// -----------------------------------------------------------------------------
void KDesktop::slotDatabaseChanged()
{
  if (m_bInit)
     slotStart();
  refreshMimeTypes();
}

// -----------------------------------------------------------------------------

void KDesktop::slotMouseButtonPressed(int _button, QIconViewItem* _item, const QPoint& _global)
{
    if (m_bInit) return;
    m_lastIcon = 0L; // user action -> not renaming an icon
    if(_item) {
        switch(_button) {
        case RightButton:
            ((KFileIVI*)_item)->setSelected( true );
            popupMenu( _global, selectedFileItems() );
            break;
        case MidButton:
            slotReturnPressed( _item );
            break;
        }
    }
    else
    {
        KRootWm::getRootWm()->mousePressed( _global, _button );
    }
}


// -----------------------------------------------------------------------------

void KDesktop::slotReturnPressed( QIconViewItem *item )
{
  kapp->propagateSessionManager();
  m_lastIcon = 0L; // user action -> not renaming an icon
  if (item) {
    visualActivate(item);
    ((KFileIVI*)item)->returnPressed();
  }
}

// -----------------------------------------------------------------------------

void KDesktop::slotCut()
{
  cutSelection();

  QByteArray data;
  QDataStream stream( data, IO_WriteOnly );
  stream << (int)true;
  kapp->dcopClient()->send( "konqueror*", "KonquerorIface", "setMoveSelection(int)", data );
  kapp->dcopClient()->send( "kdesktop", "KDesktopIface", "setMoveSelection(int)", data );
  s_bMoveSelection = true;
}

// -----------------------------------------------------------------------------

void KDesktop::slotCopy()
{
  copySelection();

  QByteArray data;
  QDataStream stream( data, IO_WriteOnly );
  stream << (int)false;
  kapp->dcopClient()->send( "konqueror*", "KonquerorIface", "setMoveSelection(int)", data );
  kapp->dcopClient()->send( "kdesktop", "KDesktopIface", "setMoveSelection(int)", data );
  s_bMoveSelection = false;
}

// -----------------------------------------------------------------------------

void KDesktop::slotPaste()
{
  pasteSelection( s_bMoveSelection );
}

void KDesktop::slotTrash()
{
  KonqOperations::del(this, KonqOperations::TRASH, selectedUrls());
}

void KDesktop::slotDelete()
{
  KonqOperations::del(this, KonqOperations::DEL, selectedUrls());
}

void KDesktop::slotShred()
{
  KonqOperations::del(this, KonqOperations::SHRED, selectedUrls());
}

// -----------------------------------------------------------------------------

void KDesktop::popupMenu( const QPoint &_global, KFileItemList _items )
{
   if (m_bInit) return;
   KonqPopupMenu * popupMenu = new KonqPopupMenu( _items,
                                                  m_url,
                                                  m_actionCollection,
                                                  KRootWm::getRootWm()->newMenu() );

   popupMenu->exec( _global );
   delete popupMenu;
}

// -----------------------------------------------------------------------------

void KDesktop::slotEnableAction( const char * name, bool enabled )
{
  QCString sName( name );
  // No such actions here... konqpopupmenu provides them.
  if ( sName == "properties" || sName == "editMimeType" )
    return;

  // Hmm, we have one action for paste, not two...
  if ( sName == "pastecopy" || sName == "pastecut" )
    sName = "paste";

  KAction * act = m_actionCollection.action( sName.data() );
  if (!act)
    kdWarning(1203) << "Unknown action " << sName.data() << " - can't enable" << endl;
  else
    act->setEnabled( enabled );
}

// -----------------------------------------------------------------------------

void KDesktop::slotClear()
{
    clear();
}

// -----------------------------------------------------------------------------

void KDesktop::slotNewItems( const KFileItemList & entries )
{
  //kdDebug() << "KDesktop::slotNewItems" << endl;
  KFileItemListIterator it(entries);
  KFileIVI* fileIVI = 0L;
  for (; it.current(); ++it)
  {
    fileIVI = new KFileIVI( this, static_cast<KonqFileItem *>(it.current()),
			    iconSize(), false );
    //kdDebug() << fileIVI->text() << endl;
    if (fileIVI->text().right(7) == QString::fromLatin1(".kdelnk"))
    {
      QString trunc(fileIVI->text());
      trunc = trunc.left(trunc.length() - 7);
      fileIVI->setText(trunc);
    }
    else if (fileIVI->text().right(8) == QString::fromLatin1(".desktop"))
    {
      QString trunc(fileIVI->text());
      trunc = trunc.left(trunc.length() - 8);
      fileIVI->setText(trunc);
    }
    fileIVI->setRenameEnabled( false );
    QObject::connect( fileIVI, SIGNAL( dropMe( KFileIVI *, QDropEvent * ) ),
                      this, SLOT( slotDropItem( KFileIVI *, QDropEvent * ) ) );

    if ( m_dotDirectory )
    {
      QString group = m_iconPositionGroupPrefix;
      group.append( it.current()->url().fileName() );
      //kdDebug() << "slotNewItems : looking for group " << group << endl;
      if ( m_dotDirectory->hasGroup( group ) )
      {
        m_dotDirectory->setGroup( group );
	m_hasExistingPos = true;
	int x = m_dotDirectory->readNumEntry( "X" );
	int y = m_dotDirectory->readNumEntry( "Y" );
	fileIVI->move( x, y );
      }
    }
  }
  if (fileIVI)
    m_lastIcon = fileIVI;
}

// -----------------------------------------------------------------------------

void KDesktop::slotRefreshItems( const KFileItemList & entries )
{
    KFileItemListIterator rit(entries);
    for (; rit.current(); ++rit)
    {
        QIconViewItem *it = firstItem();
        for ( ; it ; it = it->nextItem() )
        {
            KFileIVI * fileIVI = static_cast<KFileIVI *>(it);
            if ( fileIVI->item() == rit.current() ) // compare the pointers
            {
                fileIVI->refreshIcon( true );
                break;
            }
        }
    }
}

// -----------------------------------------------------------------------------

void KDesktop::slotDeleteItem( KFileItem * _fileitem )
{
    //kdDebug() << "KDesktop::slotDeleteItems" << endl;
    // we need to find out the KFileIVI containing the fileitem
    QIconViewItem *it = firstItem();
    while ( it ) {
      KFileIVI * fileIVI = static_cast<KFileIVI *>(it);
      if ( fileIVI->item() == _fileitem ) { // compare the pointers
        // Delete this item.
        //kdDebug() << fileIVI->text() << endl;
        // It may be that it has been renamed. In this case,
        // m_lastIcon should be moved to this icon's position.
        // (We rely on newItems being emitted before deleteItem)
        if ( m_lastIcon )
          // Problem is: I'd like to compare those two file's attributes
          // (size, creation time, modification time... etc.) but since renaming
          // is done by kpropsdlg, all of those can have changed (and creation time
          // is different since the new file is a copy!)
        {
          kdDebug() << "moving " << m_lastIcon->text() << endl;
          m_lastIcon->move( fileIVI->x(), fileIVI->y() );
          m_lastIcon = 0L;
        }
        delete fileIVI;
        break;
      }
      it = it->nextItem();
    }
}

// -----------------------------------------------------------------------------

void KDesktop::slotStarted()
{
    m_dotDirectory = new KDesktopFile( m_dotDirectoryPath, true );
}

void KDesktop::slotCompleted()
{
    // Root item ? Store in konqiconviewwidget
    if ( m_dirLister->rootItem() )
      setRootItem( static_cast<KonqFileItem *>(m_dirLister->rootItem()) );

    if ( m_dotDirectory )
    {
      delete m_dotDirectory;
      m_dotDirectory = 0;
    }

    slotSaveIconPositions();
    viewport()->repaint();
}

// -----------------------------------------------------------------------------

void KDesktop::slotItemRenamed(QIconViewItem* /*_item*/)
{
    kdDebug() << "KDesktop::slotItemRenamed()" << endl;
}

void KDesktop::slotSaveDropPosition( QDropEvent *ev, const QValueList<QIconDragItem> & )
{
    m_lastIcon = 0L;
    if (m_bInit) return; // too early
    if (m_dotDirectory) return; // we are listing the dir...
    m_dotDirectory = new KDesktopFile( m_dotDirectoryPath );
    if (ev->provides( "text/uri-list" ))
    {
        KURL::List lst;
        if ( KonqDrag::decode( ev, lst ) ) // Are they urls ?
        {
            // For now, deal with only one icon
            // TODO: if we can decode as application/x-qiconlist then we
            // can even store the position of each icon (to keep their relative position)
            if ( lst.count() == 1 )
            {
                KURL u = lst.first();
                kdDebug() << "Saving drop position for " << u.fileName() << " at " << ev->pos().x() << "," << ev->pos().y() << endl;
                m_dotDirectory->setGroup( QString( m_iconPositionGroupPrefix ).append( u.fileName() ) );
                m_dotDirectory->writeEntry( "X", ev->pos().x() );
                m_dotDirectory->writeEntry( "Y", ev->pos().y() );
            }
        }
    }
    m_dotDirectory->sync();
    delete m_dotDirectory;
}

// -----------------------------------------------------------------------------

void KDesktop::showEvent( QShowEvent *e )
{
  //HACK to avoid QIconView calling arrangeItemsInGrid (Simon)
  //EVEN MORE HACK: unfortunately, QScrollView has no concept of
  //TopToBottom, therefore, it always adds LeftToRight.  So, if any of
  //the icons have a setting, we'll use QScrollView.. but otherwise,
  //we use the iconview
  if (m_hasExistingPos)
    QScrollView::showEvent( e );
  else
    KIconView::showEvent( e );
}

// -----------------------------------------------------------------------------

// don't hide when someone presses Alt-F4 on us
void KDesktop::closeEvent(QCloseEvent *e)
{
    e->ignore();
}

// don't scroll when someone uses his nifty mouse wheel
void KDesktop::viewportWheelEvent( QWheelEvent * e )
{
    e->ignore();
}

void KDesktop::clientAreaUpdated(QRect r)
{
  // disabled until this is discussed
//  kdDebug() << "KDesktop::clientAreaAdjusted(" << r.left() << ", " << r.right() << ", " << r.top() << ", " << r.bottom() << ")" << endl;
//  setGeometry(r);
}

#include "desktop.moc"
