/* 
 *  Copyright (C) 2004 Girish Ramakrishnan All Rights Reserved.
 *	
 * This 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 software 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 General Public License
 * along with this software; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
 * USA.
 */

// $Id: customtraylabel.cpp,v 1.13 2005/04/05 17:16:12 cs19713 Exp $

#include <qsettings.h>
#include <qpopupmenu.h>
#include <qmessagebox.h>
#include <qfiledialog.h>
#include <qinputdialog.h>
#include <qaction.h>
#include <qtimer.h>
#include <qsize.h>
#include <stdlib.h>

#include "trace.h"
#include "customtraylabel.h"
#include "traylabelmgr.h"
#include "kdocker.h"

CustomTrayLabel::CustomTrayLabel(Window w, QWidget* p, const QString& t)
  : QTrayLabel(w, p, t), mUndockWhenDead(false)
{ 
  installMenu(); 
}

CustomTrayLabel::CustomTrayLabel(const QStringList& argv, pid_t pid, 
                                 QWidget* parent)
  : QTrayLabel(argv, pid, parent), mUndockWhenDead(false)
{ 
  installMenu(); 
}

/*
 * Installs a popup menu on the tray label
 */
void CustomTrayLabel::installMenu()
{
  QPixmap kdocker_png(QString(ICONS_PATH) + "/kdocker.png");
  if (kdocker_png.isNull()) 
    kdocker_png.load(qApp->applicationDirPath() + "/icons/kdocker.png");
  setIcon(kdocker_png);
  TrayLabelMgr *tlMgr = TrayLabelMgr::instance();

  mOptionsMenu = new QPopupMenu(this);
  mSessionManagement = new QAction(tr("Dock when session restored"), 0, this);
  mSessionManagement->setToggleAction(true);
  connect(mSessionManagement, SIGNAL(toggled(bool)),
          this, SLOT(enableSessionManagement(bool)));
  mSessionManagement->addTo(mOptionsMenu);

  mAutoLaunch = new QAction(tr("Launch on startup"), 0, this);
	mAutoLaunch->setToggleAction(true);
  connect(mAutoLaunch, SIGNAL(activated()), 
          this, SLOT(slotSetLaunchOnStartup()));
  mAutoLaunch->addTo(mOptionsMenu);

  mOptionsMenu->insertItem(tr("Set Icon"), this, SLOT(setCustomIcon()));

  mBalloonTimeout = new QAction(tr("Set balloon timeout"), 0, this);
  connect(mBalloonTimeout, SIGNAL(activated()), 
	        this, SLOT(slotSetBalloonTimeout()));
  mBalloonTimeout->addTo(mOptionsMenu);

  mDockWhenObscured = new QAction(tr("Dock when obscured"), 0, this);
  mDockWhenObscured->setToggleAction(true);
  connect(mDockWhenObscured, SIGNAL(toggled(bool)),
          this, SLOT(setDockWhenObscured(bool)));
  mDockWhenObscured->addTo(mOptionsMenu);

  mDockWhenMinimized = new QAction(tr("Dock when minimized"), 0, this);
  mDockWhenMinimized->setToggleAction(true);
  connect(mDockWhenMinimized, SIGNAL(toggled(bool)),
          this, SLOT(setDockWhenMinimized(bool)));
  mDockWhenMinimized->addTo(mOptionsMenu);

  mSkipTaskbar = new QAction(tr("Skip taskbar"), 0, this);
  mSkipTaskbar->setToggleAction(true);
  connect(mSkipTaskbar, SIGNAL(toggled(bool)), 
          this, SLOT(setSkipTaskbar(bool)));
  mSkipTaskbar->addTo(mOptionsMenu);

  mMainMenu = new QPopupMenu(this);
  mMainMenu->insertItem(QIconSet(kdocker_png),
                        tr("About KDocker"), tlMgr, SLOT(about()));
  mMainMenu->insertSeparator();
  mMainMenu->insertItem(tr("Options"), mOptionsMenu);
  mMainMenu->insertItem(tr("Dock Another"), tlMgr, SLOT(dockAnother()));
  mMainMenu->insertItem(tr("Undock All"), tlMgr, SLOT(undockAll()));
  mMainMenu->insertSeparator();

  mShowId = mMainMenu->insertItem(QString("Show/Hide [untitled]"), 
                                  this, SLOT(toggleShow()));
  mMainMenu->insertItem(QString(tr("Undock")), this, SLOT(undock()));
  mMainMenu->insertItem(QString(tr("Close")), this, SLOT(close()));

  connect(mMainMenu, SIGNAL(aboutToShow()), this, SLOT(updateMenu()));

  // Apply defaults here
  setLaunchOnStartup(false);
  setDockWhenObscured(false);
  enableSessionManagement(true);
  mDockWhenMinimized->setOn(isDockWhenMinimized());
  mSkipTaskbar->setOn(isSkippingTaskbar());
  setAcceptDrops(true); // and you thought this function only installs the menu
}

/*
 * Session Management
 */
bool CustomTrayLabel::restoreState(QSettings& settings)
{
  mAutoLaunch->setOn(settings.readBoolEntry("/LaunchOnStartup"));
  setDockWhenObscured(settings.readBoolEntry("/DockWhenObscured"));
  TRACE("AutoLaunch=%i DWM=%i DWO=%i", isLaunchOnStartup(), 
        isDockWhenMinimized(), isDockWhenObscured());
  return QTrayLabel::restoreState(settings);
}

bool CustomTrayLabel::saveState(QSettings& settings)
{
  if (!mSessionManagement->isOn()) return false;
  
  QTrayLabel::saveState(settings);
  settings.writeEntry("/LaunchOnStartup", isLaunchOnStartup());
  settings.writeEntry("/DockWhenObscured", isDockWhenObscured());
  TRACE("AutoLaunch=%i DWM=%i DWO=%i", isLaunchOnStartup(), 
        isDockWhenMinimized(), isDockWhenObscured());
  return true;
}

static bool which(const char *app)
{
  if (access(app, X_OK) == 0) return true;

  // Check if the program exist in the $PATH
  char *path = strdup(getenv("PATH"));
  char prog[300];
  if (path == NULL) return false;
  TRACE("PATH=%s", path);
  char *p = strtok(path, ":");
  while (p != NULL)
  {
     snprintf(prog, sizeof(prog), "%s/%s", p, app);
     if (access(prog, X_OK) == 0) break;
     p = strtok(NULL, ":");
  }
  free(path);
  TRACE("Located at (%s)", p);
  return p != NULL;
}
 
// Overridden to update our menu
void CustomTrayLabel::setDockWhenMinimized(bool dwm)
{
  QTrayLabel::setDockWhenMinimized(dwm);
  mDockWhenMinimized->setOn(isDockWhenMinimized());
}

void CustomTrayLabel::setSkipTaskbar(bool skip)
{
  QTrayLabel::setSkipTaskbar(skip);
  mSkipTaskbar->setOn(isSkippingTaskbar());
}

void CustomTrayLabel::setAppName(const QString& name)
{
  QTrayLabel::setAppName(name.lower());
}

/*
 * This function is called when QTrayLabel wants to know whether it can
 * unsubscribe from root window. This is because it doesnt know if someone
 * else is interested in root window events
 */
bool CustomTrayLabel::canUnsubscribeFromRoot(void)
{
  return (TrayLabelMgr::instance())->hiddenLabelsCount() == 0;
}

// Get icon from user, load it and if successful load it.
void CustomTrayLabel::setCustomIcon(void)
{
  QString icon;
  
  while (true)
  {
    // Nag the user to give us a valid icon or press cancel
    icon = QFileDialog::getOpenFileName();
    if (icon.isNull()) return;     // user cancelled
    if (!QPixmap(icon).isNull()) break;
    TRACE("Attempting to set icon to %s", icon.latin1());
    QMessageBox::critical(this, tr("KDocker"), 
                          tr("%1 is not a valid icon").arg(icon));
  }
    
  setTrayIcon(icon);
}

// Get balloon timeout from the user
void CustomTrayLabel::slotSetBalloonTimeout(void)
{
  bool ok;
  int timeout = QInputDialog::getInteger(tr("KDocker"), 
      tr("Enter balloon timeout (secs). 0 to disable ballooning"), 
			balloonTimeout()/1000, 0, 60, 1, &ok);
	
  if (!ok) return;
  setBalloonTimeout(timeout * 1000);
}

void CustomTrayLabel::setLaunchOnStartup(bool launch)
{
  mAutoLaunch->setOn(launch);
  slotSetLaunchOnStartup(); // fake an "activated" signal
}

void CustomTrayLabel::slotSetLaunchOnStartup()
{
  TRACE("%i", mAutoLaunch->isOn());
  if (!mAutoLaunch->isOn()) return;
  QString app = appName();

  TRACE("Validating %s", app.latin1());

  while (true)
  {
    if (which(app.latin1()))
    {
      TRACE("Autolaunch enabled to %s", app.latin1());
      setAppName(app);
      mAutoLaunch->setOn(true);
      return;
    }

    // Request user to provide file name himself
    if (QMessageBox::critical(NULL, tr("KDocker"),
        tr("\"%1\" is not a valid executable "
	  	    "or was not found in your $PATH").arg(app),
        tr("Select program"), tr("Cancel")) == 1) 
    {
      mAutoLaunch->setOn(false);
      return; // cancelled
    }

    app = QFileDialog::getOpenFileName();
    if (app.isNull()) 
    {
      TRACE("Disabling auto launch");
      mAutoLaunch->setOn(false);
      return;
    }
  }
}

// Called when we are just about to display the menu
void CustomTrayLabel::updateMenu(void)
{
  QString title = appClass(); // + "(" + appTitle() + ")";
  mMainMenu->changeItem(mShowId, QIconSet(*pixmap()),
      QString((isWithdrawn() ? tr("Show %1") : tr("Hide %1")).arg(title)));
}

void CustomTrayLabel::mapEvent(void)
{
  TRACE("mapEvent");
  if (mDockWhenObscured->isOn())
  {
    /*
   * We get a obscured event for the time between the map and focus in of 
   * the window. So we disable it for sometime and reanable.
   */
    mDockWhenObscured->setOn(false);
    QTimer::singleShot(800, mDockWhenObscured, SLOT(toggle()));
    TRACE("Turning off DWO for some time");
  }
}

void CustomTrayLabel::obscureEvent(void)
{
  TRACE("obscureEvent");
  if (mDockWhenObscured->isOn() && !isWithdrawn()) 
    withdraw();
}

void CustomTrayLabel::mouseReleaseEvent(QMouseEvent * ev)
{  
  if (ev->button() == Qt::RightButton)
    mMainMenu->popup(ev->globalPos());
  else
    toggleShow();
}

void CustomTrayLabel::destroyEvent(void)
{
  mUndockWhenDead = true;
  QTrayLabel::destroyEvent();
}

void CustomTrayLabel::processDead(void)
{
  /*
   * This is a ugly hack but worth every but of ugliness IMO ;).
   * Lets say, an instance of xmms, already exists. You type kdocker xmms.
   * KDocker launches xmms. xmms cowardly exists seeing its previous instance.
   * Wouldnt it be nice now to dock the previous instance of xmms automatically.
   * This is more common than you think (think of session restoration)
   */
   
  if (!mUndockWhenDead) 
  {
    scanClients();
    if (dockedWindow() != None) return;
  }
  undock();
}

/*
 * Can dock this window iff not docked by another one tray label already
 */
bool CustomTrayLabel::canDockWindow(Window w)
{
  TRACE("Checking if 0x%x is already docked", (unsigned) w);
  return !(TrayLabelMgr::instance()->isWindowDocked(w));
}

void CustomTrayLabel::dropEvent(QDropEvent *)
{
  QMessageBox::information(NULL, "KDocker", 
    tr("You cannot drop an item into the tray icon. Drop it on the window\n"
       "that is brought in front when you hover the item over the tray icon"));
}

