/*
 * 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: qtraylabel.cpp,v 1.31 2005/06/21 10:04:36 cs19713 Exp $

// Include all TQt includes before X
#include <tqstring.h>
#include <tqevent.h>
#include <tqfileinfo.h>
#include <tqimage.h>
#include <tqinputdialog.h>
#include <tqpixmap.h>
#include <tqpoint.h>
#include <tqtooltip.h>
#include <tqtimer.h>

#include <khelpmenu.h>
#include <kiconloader.h>
#include <kstdaction.h>
#include <kstdguiitem.h>

#include <tdeconfig.h>
#include <tdefiledialog.h>
#include <tdeglobal.h>
#include <tdelocale.h>
#include <tdemessagebox.h>
#include <tdepopupmenu.h>

#include <X11/cursorfont.h>
#include <X11/xpm.h>
#include <X11/Xmu/WinUtil.h>

#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include "util.h"

#include "trace.h"
#include "traylabelmgr.h"
#include "tqtraylabel.h"

void TQTrayLabel::initialize(void)
{
  mDocked = false;
  mWithdrawn = true;
  mBalloonTimeout = 4000;
  mUndockWhenDead = false;
  mDesktop = 666; // setDockedWindow would set it a saner value

  // Balloon's properties are set to match a TQt tool tip (see TQt source)
  mBalloon = new TQLabel(0, "balloon", WType_TopLevel | WStyle_StaysOnTop |
                                      WStyle_Customize | WStyle_NoBorder |
                                      WStyle_Tool | WX11BypassWM);
  mBalloon->setFont(TQToolTip::font());
  mBalloon->setPalette(TQToolTip::palette());
  mBalloon->setAlignment(TQt::AlignLeft | TQt::AlignTop);
  mBalloon->setAutoMask(FALSE);
  mBalloon->setAutoResize(true);
  setAlignment(TQt::AlignCenter);
  setBackgroundMode(X11ParentRelative);

  connect(&mRealityMonitor, TQ_SIGNAL(timeout()), this, TQ_SLOT(realityCheck()));
  setDockedWindow(mDockedWindow);

  sysTrayStatus(TQPaintDevice::x11AppDisplay(), &mSysTray);
  // Subscribe to system tray window notifications
  if (mSysTray != None)
    subscribe(TQPaintDevice::x11AppDisplay(), mSysTray,
              StructureNotifyMask, true);

  installMenu();
}

// Describe ourselves in a few words
const char *TQTrayLabel::me(void) const
{
  static char temp[100];
  snprintf(temp, sizeof(temp), "(%s,PID=%i,WID=0x%x)",
      appName().local8Bit().data(), mPid, (unsigned) mDockedWindow);
  return temp;
}

TQTrayLabel::TQTrayLabel(Window w, TQWidget *parent, const TQString &text)
  : TQLabel(parent, text.utf8(), WStyle_Customize | WStyle_NoBorder | WStyle_Tool),
    mDockedWindow(w), mPid(0)
{
  initialize();
}

TQTrayLabel::TQTrayLabel(const TQStringList &pname, pid_t pid, TQWidget *parent)
 : TQLabel(parent, "TrayLabel", WStyle_Customize | WStyle_NoBorder | WStyle_Tool),
   mDockedWindow(None), mProgName(pname), mPid(pid)
{
  initialize();
}

TQTrayLabel::~TQTrayLabel()
{
  TRACE("%s Goodbye", me());
  if (mDockedWindow == None) return;
  // Leave the docked window is some sane state
  mSkipTaskbar->setChecked(false);
  skipTaskbar();
  map();
}

void TQTrayLabel::setAppName(const TQString &prog)
{
  // FIXME HACK
  // using "prog.lower()" relies on window and application name being the same.
  if (mProgName.count() == 0)
  {
    mProgName.push_front(prog.lower());
  }
}

/*
 * Scans the windows in the desktop and checks if a window exists that we might
 * be interested in
 */
void TQTrayLabel::scanClients()
{
   Window r, parent, *children;
   unsigned nchildren = 0;
   Display *display = TQPaintDevice::x11AppDisplay();
   TQString ename = TQFileInfo(appName()).fileName(); // strip out the path

   XQueryTree(display, tqt_xrootwin(), &r, &parent, &children, &nchildren);
   TRACE("%s nchildren=%i", me(), nchildren);
   for(unsigned i=0; i<nchildren; i++)
   {
     Window w = XmuClientWindow(display, children[i]);
     TRACE("\t%s checking(1) 0x%x", me(), (unsigned) w);
     if (!isNormalWindow(display, w)) continue;
     if (analyzeWindow(display, w, mPid, ename.local8Bit()))
     {
       TRACE("\t%s SOULMATE FOUND (1)", me());
       setDockedWindow(w);
       return;
     }
   }
}

/*
 * Do a reality check :). Note that this timer runs only when required. Does 3
 * things,
 *  1) If the system tray had disappeared, checks for arrival of new system tray
 *  2) Check root window subscription since it is overwritten by TQt (see below)
 *  3) Checks health of the process whose windows we are docking
 */
void TQTrayLabel::realityCheck(void)
{
  if (mSysTray == None)
  {
    // Check the system tray status if we were docked
    if (sysTrayStatus(TQPaintDevice::x11AppDisplay(), &mSysTray)
            != SysTrayPresent) return; // no luck

    TRACE("%s System tray present", me());
    dock();
    subscribe(TQPaintDevice::x11AppDisplay(), mSysTray,
              StructureNotifyMask, true);
    mRealityMonitor.stop();
    return;
  }

  /*
   * I am not sure when, but TQt at some point in time overwrites our
   * subscription (SubstructureNotifyMask) on the root window. So, we check
   * the status of root window subscription periodically. Now, from the time
   * TQt overwrote our subscription to the time we discovered it, the
   * window we are looking for could have been mapped and we would have never
   * been informed (since TQt overrwrote the subscription). So we have to
   * scan existing client list and dock. I have never seen this happen
   * but I see it likely to happen during session restoration
   */
  Display *display = TQPaintDevice::x11AppDisplay();
  XWindowAttributes attr;
  XGetWindowAttributes(display, tqt_xrootwin(), &attr);

  if (!(attr.your_event_mask & SubstructureNotifyMask))
  {
    subscribe(display, None, SubstructureNotifyMask, true);
    TRACE("%s rescanning clients since tqt overrode mask", me());
    scanClients();
  }

  if (mPid)
  {
     // Check process health
    int status;
    if (waitpid(mPid, &status, WNOHANG) == 0) return; // still running
    TRACE("%s processDead", me());
    mPid = 0;
    processDead();
  }
}

/*
 * Sends a message to the WM to show this window on all the desktops
 */
void TQTrayLabel::showOnAllDesktops(void)
{
  TRACE("Showing on all desktops");
  Display *d = TQPaintDevice::x11AppDisplay();
  long l[5] = { -1, 0, 0, 0, 0 }; // -1 = all, 0 = Desktop1, 1 = Desktop2 ...
  sendMessage(d, tqt_xrootwin(), mDockedWindow, "_NET_WM_DESKTOP", 32,
              SubstructureNotifyMask | SubstructureRedirectMask, l, sizeof(l));
}

// System tray messages
const long SYSTEM_TRAY_REQUEST_DOCK = 0;
const long SYSTEM_TRAY_BEGIN_MESSAGE = 1;
const long SYSTEM_TRAY_CANCEL_MESSAGE = 2;

/*
 * Add the window to the system tray. Different WM require different hints to be
 * set. We support the following (Google for more information),
 * 1. GNOME - SYSTEM_TRAY_REQUEST_DOCK (freedesktop.org)
 * 2. KDE 3.x and above - _KDE_NET_WM_SYSTEM_TRAY_WINDOW_FOR
 * 3. Older KDE - KWM_DOCKWINDOW (Untested)
 */
void TQTrayLabel::dock(void)
{
  TRACE("%s", me());
  mDocked = true;
  if (mDockedWindow == None) return;      // nothing to add

  if (mSysTray == None)                   // no system tray yet
  {
    TRACE("%s starting reality monitor", me());
    mRealityMonitor.start(500);
    return;
  }

  Display *display = TQPaintDevice::x11AppDisplay();
  Window wid = winId();

  // 1. GNOME and NET WM Specification
  unsigned long l[5] = { CurrentTime, SYSTEM_TRAY_REQUEST_DOCK, wid, 0, 0 };
  sendMessage(display, mSysTray, mSysTray, "_NET_SYSTEM_TRAY_OPCODE",
              32, 0L, l, sizeof(l));

  // 2. KDE 3.x and above
  Atom tray_atom =
    XInternAtom(display, "_KDE_NET_WM_SYSTEM_TRAY_WINDOW_FOR", False);
  XChangeProperty(display, wid, tray_atom, XA_WINDOW, 32,
                  PropModeReplace, (unsigned char *) &wid, 1);

  // 3. All other KDEs
  tray_atom = XInternAtom(display, "KWM_DOCKWINDOW", False);
  XChangeProperty(display, wid, tray_atom, XA_WINDOW, 32,
                  PropModeReplace, (unsigned char *) &wid, 1);

  TRACE("%s ", me());

  handleTitleChange();
  handleIconChange();

  if (mProgName.count() == 0)
  {
    setAppName(mClass);
  }
  mDockWhenRestored->setChecked(true);
  setDockWhenRestored(true);

  /*
   * For Gnome, a delay is required before we do a show (dont ask me why)
   * If I do a show() without any delay, sometimes the icon has width=1 pixel
   * even though the minimumSizeHint = 24, 24. I have successfully got it
   * working with with a delay of as little as 50ms. But since I
   * dont understand why this delay is required, I am justifiably paranoid
   */
  TQTimer::singleShot(500, this, TQ_SLOT(show()));

  // let the world know
  emit docked(this);
  emit docked();
}

/*
 * Undocks. Removes us from the system tray. The spec doesnt say how an icon
 * can be removed from the tray. KDE Spec says XUnmapWindow or XWithdraw should
 * be used. It works but the system tray does not fill the void that we left
 * in the tray. Looks like the system tray will resize only for DestroyEvents
 */
void TQTrayLabel::undock(void)
{
  TRACE("%s stopping reality monitor", me());
  mRealityMonitor.stop();
  XUnmapWindow(TQPaintDevice::x11AppDisplay(), winId());
  emit undocked(this);
  emit undocked();
}

/*
 * Maps the window from the same place it was withdrawn from
 */
void TQTrayLabel::map(void)
{
  TRACE("%s", me());
  mWithdrawn = false;
  if (mDockedWindow == None) return;

  Display *display = TQPaintDevice::x11AppDisplay();

  if (mDesktop == -1)
  {
      /*
       * We track _NET_WM_DESKTOP changes in the x11EventFilter. Its used here.
       * _NET_WM_DESKTOP is set by the WM to the active desktop for newly
       * mapped windows (like this one) at some point in time. We give
       * the WM 200ms to do that. We will override that value to -1 (all
       * desktops) on showOnAllDesktops().
       */
       TQTimer::singleShot(200, this, TQ_SLOT(showOnAllDesktops()));
  }

  /*
   * A simple XMapWindow would not do. Some applications like xmms wont
   * redisplay its other windows (like the playlist, equalizer) since the
   * Withdrawn->Normal state change code does not map them. So we make the
   * window go through Withdrawn->Iconify->Normal state.
   */
  XWMHints *wm_hint = XGetWMHints(display, mDockedWindow);
  if (wm_hint)
  {
    wm_hint->initial_state = IconicState;
    XSetWMHints(display, mDockedWindow, wm_hint);
    XFree(wm_hint);
  }

  XMapWindow(display, mDockedWindow);
  mSizeHint.flags = USPosition;  // Obsolete ?
  XSetWMNormalHints(display, mDockedWindow, &mSizeHint);
  // make it the active window
  long l[5] = { None, CurrentTime, None, 0, 0 };
  sendMessage(display, tqt_xrootwin(), mDockedWindow, "_NET_ACTIVE_WINDOW", 32,
              SubstructureNotifyMask | SubstructureRedirectMask, l, sizeof(l));
  // skipTaskbar modifies _NET_WM_STATE. Make sure we dont override WMs value
  TQTimer::singleShot(230, this, TQ_SLOT(skipTaskbar()));
  // disable "dock when minized" (if enable) for a short while since we went to Iconic state
  // (when the window is mapped, often an IconicState WM_STATE message is sent too
  // just before the NormalState)
  mSavedDWM = mDockWhenMinimized->isChecked(); // store for later use
  mDockWhenMinimized->setChecked(false);
  TQTimer::singleShot(500, this, TQ_SLOT(toggleDockWhenMinimized()));
}

void TQTrayLabel::withdraw(void)
{
  TRACE("%s", me());
  mWithdrawn = true;
  if (mDockedWindow == None) return;

  Display *display = TQPaintDevice::x11AppDisplay();
  int screen = DefaultScreen(display);
  long dummy;

  XGetWMNormalHints(display, mDockedWindow, &mSizeHint, &dummy);

  /*
   * A simple call to XWithdrawWindow wont do. Here is what we do:
   * 1. Iconify. This will make the application hide all its other windows. For
   *    example, xmms would take off the playlist and equalizer window.
   * 2. Next tell the WM, that we would like to go to withdrawn state. Withdrawn
   *    state will remove us from the taskbar.
   *    Reference: ICCCM 4.1.4 Changing Window State
   */
  XIconifyWindow(display, mDockedWindow, screen);  // good for effects too
  XUnmapWindow(display, mDockedWindow);
  XUnmapEvent ev;
  memset(&ev, 0, sizeof(ev));
  ev.type = UnmapNotify;
  ev.display = display;
  ev.event = tqt_xrootwin();
  ev.window = mDockedWindow;
  ev.from_configure = false;
  XSendEvent(display, tqt_xrootwin(), False,
             SubstructureRedirectMask|SubstructureNotifyMask, (XEvent *)&ev);
  XSync(display, False);
}

/*
 * Skipping the taskbar is a bit painful. Basically, NET_WM_STATE needs to
 * have _NET_WM_STATE_SKIP_TASKBAR. NET_WM_STATE needs to be updated
 * carefully since it is a set of states.
 */
void TQTrayLabel::skipTaskbar(void)
{
  Atom __attribute__ ((unused)) type;
  int __attribute__ ((unused)) format;
  unsigned long __attribute__ ((unused)) left;
  Atom *data = NULL;
  unsigned long nitems = 0, num_states = 0;
  Display *display = TQPaintDevice::x11AppDisplay();

  TRACE("%s", me());
  Atom _NET_WM_STATE = XInternAtom(display, "_NET_WM_STATE", True);
  Atom skip_atom = XInternAtom(display, "_NET_WM_STATE_SKIP_TASKBAR", False);
  int ret = XGetWindowProperty(display, mDockedWindow, _NET_WM_STATE, 0,
                               20, False, AnyPropertyType, &type, &format,
                               &nitems, &left, (unsigned char **) &data);
  Atom *old_states = (Atom *) data;
  bool append = true, replace = false;

  if ((ret == Success) && data)
  {
    // Search for the skip_atom. Stop when found
    for (num_states = 0; num_states < nitems; num_states++)
      if (old_states[num_states] == skip_atom) break;

    if (mSkipTaskbar->isChecked())
    {
      append = (num_states >= nitems);
    }
    else
    {
      if (num_states < nitems)
      {
        replace = true;    // need to remove skip_atom
        for (; num_states < nitems - 1; num_states++)
          old_states[num_states] = old_states[num_states + 1];
      }
    }
    XFree(data);
  }

  TRACE("%s SkippingTaskar=%i append=%i replace=%i", me(),
        mSkipTaskbar->isChecked(), append, replace);

  if (mSkipTaskbar->isChecked())
  {
    if (append)
    {
      XChangeProperty(display, mDockedWindow, _NET_WM_STATE, XA_ATOM, 32,
                      PropModeAppend, (unsigned char *) &skip_atom, 1);
    }
  }
  else if (replace)
  {
    XChangeProperty(display, mDockedWindow, _NET_WM_STATE, XA_ATOM, 32,
                    PropModeReplace, (unsigned char *) &old_states, nitems - 1);
  }
}

void TQTrayLabel::setSkipTaskbar(bool skip)
{
  TRACE("%s skip=%i", me(), skip);
  if (skip != mSkipTaskbar->isChecked())
  {
    // Make sure the toggle action state is updated in case this function
    // is called directly from code.
    mSkipTaskbar->setChecked(skip);
    return;
  }
  if (mDockedWindow != None && !mWithdrawn)
  {
    skipTaskbar();
  }
}

void TQTrayLabel::toggleShow(void)
{
  if (mWithdrawn)
  {
    map();
  }
  else
  {
    withdraw();
  }
}

/*
 * Closes a window by sending _NET_CLOSE_WINDOW. For reasons best unknown we
 * need to first map and then send the request.
 */
void TQTrayLabel::close(void)
{
  TRACE("%s", me());
  undock();
  Display *display = TQPaintDevice::x11AppDisplay();
  long l[5] = { 0, 0, 0, 0, 0 };
  map();
  sendMessage(display, tqt_xrootwin(), mDockedWindow, "_NET_CLOSE_WINDOW", 32,
              SubstructureNotifyMask | SubstructureRedirectMask,
              l, sizeof(l));
}

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

/*
 * Sets the tray icon. If the icon failed to load, we revert to application icon
 */
void TQTrayLabel::setTrayIcon(const TQString& icon)
{
  mCustomIcon = icon;
  if (TQPixmap(mCustomIcon).isNull()) mCustomIcon = TQString::null;
  TRACE("%s mCustomIcon=%s", me(), mCustomIcon.local8Bit());
  updateIcon();
}

/*
 * Sets the docked window to w.
 *   A) Start/stop reality timer.
 *   B) Subscribe/Unsubscribe for root/w notifications as appropriate
 *   C) And of course, dock the window and apply some settings
 */
void TQTrayLabel::setDockedWindow(Window w)
{
  TRACE("%s %s reality monitor", me(),
        mDockedWindow==None ? "Starting" : "Stopping");

  // Check if we are allowed to dock this window (allows custom rules)
  mDockedWindow = None;
  if (w != None)
  {
    if (!(TrayLabelMgr::instance()->isWindowDocked(w)))
    {
      mDockedWindow = w;
    }
  }

  if (mDockedWindow == None) mRealityMonitor.start(500); else mRealityMonitor.stop();

  Display *d = TQPaintDevice::x11AppDisplay();

  // Subscribe for window or root window events
  if (w == None) subscribe(d, None, SubstructureNotifyMask, true);
  else
  {
    if (canUnsubscribeFromRoot())
      subscribe(d, None, ~SubstructureNotifyMask, false);
    else subscribe(d, None, SubstructureNotifyMask, true);

    subscribe(d, w,
              StructureNotifyMask | PropertyChangeMask |
              VisibilityChangeMask | FocusChangeMask,
              true);
  }

  if (mDocked && w!=None)
  {
    // store the desktop on which the window is being shown
    getCardinalProperty(d, mDockedWindow,
                        XInternAtom(d, "_NET_WM_DESKTOP", True), &mDesktop);

    if (mWithdrawn)
    {
      // show the window for sometime before docking
      TQTimer::singleShot(500, this, TQ_SLOT(withdraw()));
    }
    else map();
    dock();
  }
}

/*
 * Balloon text. Overload this if you dont like the way things are ballooned
 */
void TQTrayLabel::balloonText()
{
  TRACE("%s BalloonText=%s ToolTipText=%s", me(),
        mBalloon->text().local8Bit(), TQToolTip::textFor(this).local8Bit());

  if (mBalloon->text() == TQToolTip::textFor(this)) return;
#if 0 // I_GOT_NETWM_BALLOONING_TO_WORK
  // if you can get NET WM ballooning to work let me know
  static int id = 1;
  long l[5] = { CurrentTime, SYSTEM_TRAY_BEGIN_MESSAGE, 2000,
                mTitle.length(), id++
  };
  sendMessage(display, mSystemTray, winId(), "_NET_SYSTEM_TRAY_OPCODE", 32,
              SubstructureNotifyMask | SubstructureRedirectMask,
              l, sizeof(l));
  int length = mTitle.length();
  const char *data = mTitle.local8Bit();
  while (length > 0)
  {
    sendMessage(display, mSystemTray, winId(), "_NET_SYSTEM_TRAY_MESSAGE_DATA", 8,
                SubstructureNotifyMask | SubstructureRedirectMask,
                (void *) data, length > 20 ? 20 : length);
    length -= 20;
    data += 20;
  }
#else
  // Manually do ballooning. See the TQt ToolTip code
  TQString oldText = mBalloon->text();
  mBalloon->setText(TQToolTip::textFor(this));
  if (oldText.isEmpty()) return; // dont tool tip the first time
  TQPoint p = mapToGlobal(TQPoint(0, -1 - mBalloon->height()));
  if (p.x() + mBalloon->width() > TQApplication::desktop()->width())
    p.setX(p.x() + width() - mBalloon->width());

  if (p.y() < 0) p.setY(height() + 1);

  mBalloon->move(p);
  mBalloon->show();
  TQTimer::singleShot(mBalloonTimeout, mBalloon, TQ_SLOT(hide()));
#endif
}

/*
 * Update the title in the menu. Balloon the title change if necessary
 */
void TQTrayLabel::handleTitleChange(void)
{
  Display *display = TQPaintDevice::x11AppDisplay();
  char *window_name = NULL;

  XFetchName(display, mDockedWindow, &window_name);
  mTitle = window_name;
  TRACE("%s has title [%s]", me(), mTitle.local8Bit());
  if (window_name) XFree(window_name);

  XClassHint ch;
  if (XGetClassHint(display, mDockedWindow, &ch))
  {
    if (ch.res_class) mClass = TQString(ch.res_class);
    else if (ch.res_name) mClass = TQString(ch.res_name);

    if (ch.res_class) XFree(ch.res_class);
    if (ch.res_name) XFree(ch.res_name);
  }

  updateTitle();
  if (mBalloonTimeout) balloonText();
}

/*
 * Overload this if you want a tool tip format that is different from the one
 * below i.e "Title [Class]".
 */
void TQTrayLabel::updateTitle()
{
  TRACE("%s", me());
  TQString text = mTitle + " [" + mClass + "]";
  TQToolTip::remove(this);
  TQToolTip::add(this, text);

  if (mBalloonTimeout) balloonText();
}

void TQTrayLabel::handleIconChange(void)
{
  char **window_icon = NULL;

  TRACE("%s", me());
  if (mDockedWindow == None) return;

  Display *display = TQPaintDevice::x11AppDisplay();
  XWMHints *wm_hints = XGetWMHints(display, mDockedWindow);
  if (wm_hints != NULL)
  {
    if (!(wm_hints->flags & IconMaskHint))
      wm_hints->icon_mask = None;
    /*
     * We act paranoid here. Progams like KSnake has a bug where
     * IconPixmapHint is set but no pixmap (Actually this happens with
     * quite a few KDE programs) X-(
     */
    if ((wm_hints->flags & IconPixmapHint) && (wm_hints->icon_pixmap))
      XpmCreateDataFromPixmap(display, &window_icon, wm_hints->icon_pixmap,
                              wm_hints->icon_mask, NULL);
    XFree(wm_hints);
  }
  TQImage image;
  if (!window_icon)
  {
    image = TDEGlobal::iconLoader()->loadIcon("question", TDEIcon::NoGroup, TDEIcon::SizeMedium);
  }
  else image = TQPixmap((const char **) window_icon).convertToImage();
  if (window_icon) XpmFree(window_icon);
  mAppIcon = image.smoothScale(24, 24); // why?
  setMinimumSize(mAppIcon.size());
  setMaximumSize(mAppIcon.size());

  updateIcon();
}

/*
 * Overload this to possibly do operations on the pixmap before it is set
 */
void TQTrayLabel::updateIcon()
{
  TRACE("%s", me());
  setPixmap(mCustomIcon.isEmpty() ? mAppIcon : mCustomIcon);
  erase();
  TQPaintEvent pe(rect());
  paintEvent(&pe);
}

/*
 * Mouse activity on our label. RightClick = Menu. LeftClick = Toggle Map
 */
void TQTrayLabel::mouseReleaseEvent(TQMouseEvent *ev)
{
  if (ev->button() == TQt::RightButton)
  {
    mMainMenu->popup(ev->globalPos());
/*    contextMenuAboutToShow(contextMenu());
    contextMenu()->popup(e->globalPos());
    e->accept();
    return;*/
  }
  else
    toggleShow();
}

/*
 * Track drag event
 */
void TQTrayLabel::dragEnterEvent(TQDragEnterEvent *ev)
{
  ev->accept();
  map();
}

void TQTrayLabel::dropEvent(TQDropEvent *)
{
  KMessageBox::error(NULL, i18n("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"), i18n("TDEDocker"));
}

/*
 * Event dispatcher
 */
bool TQTrayLabel::x11EventFilter(XEvent *ev)
{
  XAnyEvent *event = (XAnyEvent *)ev;

  if (event->window == mSysTray)
  {
    if (event->type != DestroyNotify)
    {
      return false; // not interested in others
    }
    emit sysTrayDestroyed();
    mSysTray = None;
    TRACE("%s SystemTray disappeared. Starting timer", me());
    mRealityMonitor.start(500);
    return true;
  }
  else if (event->window == mDockedWindow)
  {
    if (event->type == DestroyNotify)
      destroyEvent();
    else if (event->type == PropertyNotify)
      propertyChangeEvent(((XPropertyEvent *)event)->atom);
    else if (event->type == VisibilityNotify)
    {
      if (((XVisibilityEvent *) event)->state == VisibilityFullyObscured)
        obscureEvent();
    }
    else if (event->type == MapNotify)
    {
      mWithdrawn = false;
      mapEvent();
    }
    else if (event->type == UnmapNotify)
    {
      mWithdrawn = true;
      unmapEvent();
    }
    else if (event->type == FocusOut)
    {
      focusLostEvent();
    }
    return true; // Dont process this again
  }

  if (mDockedWindow != None || event->type != MapNotify) return FALSE;

  TRACE("%s Will analyze window 0x%x", me(), (int)((XMapEvent *)event)->window);
  // Check if this window is the soulmate we are looking for
  Display *display = TQPaintDevice::x11AppDisplay();
  Window w = XmuClientWindow(display, ((XMapEvent *) event)->window);
  if (!isNormalWindow(display, w)) return FALSE;
  if (!analyzeWindow(display, w, mPid, TQFileInfo(appName()).fileName().local8Bit()))
  {
    return false;
  }
  // All right. Lets dock this baby
  setDockedWindow(w);
  return true;
}

void TQTrayLabel::destroyEvent(void)
{
  TRACE("%s destroyEvent", me());
  mUndockWhenDead = true;
  setDockedWindow(None);
  if (!mPid)
  {
    undock();
  }
}

void TQTrayLabel::focusLostEvent()
{
  if (mDockWhenFocusLost->isChecked())
  {
    withdraw();
  }
}

void TQTrayLabel::mapEvent(void)
{
  TRACE("mapEvent");
  if (mDockWhenObscured->isChecked())
  {
    /*
   * 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->setChecked(false);
    TQTimer::singleShot(800, mDockWhenObscured, TQ_SLOT(toggle()));
    TRACE("Turning off DWO for some time");
  }
}

void TQTrayLabel::minimizeEvent(void)
{
  TRACE("minimizeEvent");
  if (mDockWhenMinimized->isChecked())
  {
    withdraw();
  }
}

void TQTrayLabel::obscureEvent(void)
{
  TRACE("obscureEvent");
  if (mDockWhenObscured->isChecked() && !mWithdrawn)
  {
    withdraw();
  }
}

void TQTrayLabel::unmapEvent(void)
{
  // NO OP
}

void TQTrayLabel::propertyChangeEvent(Atom property)
{
  Display *display = TQPaintDevice::x11AppDisplay();
  static Atom WM_NAME = XInternAtom(display, "WM_NAME", True);
  static Atom WM_ICON = XInternAtom(display, "WM_ICON", True);
  static Atom WM_STATE = XInternAtom(display, "WM_STATE", True);
  static Atom _NET_WM_STATE = XInternAtom(display, "_NET_WM_STATE", True);
  static Atom _NET_WM_DESKTOP = XInternAtom(display, "_NET_WM_DESKTOP", True);

  if (property == WM_NAME)
    handleTitleChange();
  else if (property == WM_ICON)
    handleIconChange();
  else if (property == _NET_WM_STATE)
    ; // skipTaskbar();
  else if (property == _NET_WM_DESKTOP)
  {
      TRACE("_NET_WM_DESKTOP changed");
      getCardinalProperty(display, mDockedWindow, _NET_WM_DESKTOP, &mDesktop);
  }
  else if (property == WM_STATE)
  {
    Atom type = None;
    int format;
    unsigned long nitems, after;
    unsigned char *data = NULL;
    int r = XGetWindowProperty(display, mDockedWindow, WM_STATE,
             0, 1, False, AnyPropertyType, &type,
             &format, &nitems, &after, &data);

    if ((r == Success) && data && (*(long *) data == IconicState))
    {
      minimizeEvent();
      XFree(data);
    }
  }
}

void TQTrayLabel::processDead(void)
{
  /*
   * This is a ugly hack but worth every but of ugliness IMO ;).
   * Lets say, an instance of xmms, already exists. You type tdedocker xmms.
   * TDEDocker 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();
}

void TQTrayLabel::setDockWhenRestored(bool dwr)
{
  if (dwr && !mSessionManaged)
  {
    // Make sure the TDE action is off if session management was initially disabled by command line
    mSessionManaged = true;
    mDockWhenRestored->setChecked(false);
    return;
  }

  if (dwr && appName().isEmpty())
  {
    KMessageBox::error(NULL, i18n("No valid application executable file known. \"Dock When Restore\" is not possible."),
                        i18n("TDEDocker"));
    mDockWhenRestored->setChecked(false);
  }
}

// Get icon from user, load it and if successful load it.
void TQTrayLabel::setCustomIcon(void)
{
  TQString icon;

  while (true)
  {
    // Nag the user to give us a valid icon or press cancel
    icon = KFileDialog::getOpenFileName();
    if (icon.isEmpty()) return;     // user cancelled
    if (!TQPixmap(icon).isNull()) break;
    TRACE("Attempting to set icon to %s", icon.local8Bit());
    KMessageBox::error(this, i18n("%1 is not a valid icon").arg(icon), i18n("TDEDocker"));
  }

  setTrayIcon(icon);
}

// Get balloon timeout from the user
void TQTrayLabel::slotSetBalloonTimeout(void)
{
  bool ok;
  int timeout = TQInputDialog::getInteger(i18n("TDEDocker"),
      i18n("Enter balloon timeout (secs). 0 to disable ballooning"),
      balloonTimeout()/1000, 0, 60, 1, &ok);

  if (!ok) return;
  setBalloonTimeout(timeout * 1000);
}

// Installs a popup menu on the tray label
void TQTrayLabel::installMenu()
{
  TQPixmap tdedocker_png(TDEGlobal::iconLoader()->loadIcon("tdedocker", TDEIcon::NoGroup, TDEIcon::SizeSmall));
  setIcon(tdedocker_png);
  TrayLabelMgr *tlMgr = TrayLabelMgr::instance();

  mOptionsMenu = new TDEPopupMenu(this);
  mDockWhenRestored = new TDEToggleAction(i18n("Dock when session restored"), 0, this);
  connect(mDockWhenRestored, TQ_SIGNAL(toggled(bool)), this, TQ_SLOT(setDockWhenRestored(bool)));
  mDockWhenRestored->plug(mOptionsMenu);

  mOptionsMenu->insertItem(i18n("Set Icon"), this, TQ_SLOT(setCustomIcon()));

  mBalloonTimeoutAction = new TDEAction(i18n("Set balloon timeout"), 0, this);
  connect(mBalloonTimeoutAction, TQ_SIGNAL(activated()), this, TQ_SLOT(slotSetBalloonTimeout()));
  mBalloonTimeoutAction->plug(mOptionsMenu);

  mDockWhenObscured = new TDEToggleAction(i18n("Dock when obscured"), 0, this);
  mDockWhenObscured->plug(mOptionsMenu);

  mDockWhenMinimized = new TDEToggleAction(i18n("Dock when minimized"), 0, this);
  mDockWhenMinimized->plug(mOptionsMenu);

  mDockWhenFocusLost = new TDEToggleAction(i18n("Dock when focus lost"), 0, this);
  mDockWhenFocusLost->plug(mOptionsMenu);

  mSkipTaskbar = new TDEToggleAction(i18n("Skip taskbar"), 0, this);
  connect(mSkipTaskbar, TQ_SIGNAL(toggled(bool)), this, TQ_SLOT(setSkipTaskbar(bool)));
  mSkipTaskbar->plug(mOptionsMenu);

  mMainMenu = new TDEPopupMenu(this);
  mMainMenu->insertItem(i18n("Options"), mOptionsMenu);
  mMainMenu->insertItem(i18n("Dock Another"), tlMgr, TQ_SLOT(dockAnother()));
  mMainMenu->insertItem(i18n("Undock All"), tlMgr, TQ_SLOT(undockAll()));
  mMainMenu->insertItem(i18n("Quit All"), tlMgr, TQ_SLOT(quitAll()));
  mMainMenu->insertSeparator();

  mShowId = mMainMenu->insertItem(TQString("Show/Hide [untitled]"), this, TQ_SLOT(toggleShow()));
  mMainMenu->insertItem(TQString(i18n("Undock")), this, TQ_SLOT(undock()));
  mMainMenu->insertSeparator();

  mMainMenu->insertItem(SmallIcon("help"),KStdGuiItem::help().text(), (new KHelpMenu(this, TDEGlobal::instance()->aboutData()))->menu(), false);
  TDEAction *quitAction = KStdAction::quit(this, TQ_SLOT(close()), NULL);
  quitAction->plug(mMainMenu);

  connect(mMainMenu, TQ_SIGNAL(aboutToShow()), this, TQ_SLOT(updateMenu()));

  // Apply defaults here
  mDockWhenObscured->setChecked(false);
  mSessionManaged = true;
  mDockWhenMinimized->setChecked(true);
  mSkipTaskbar->setChecked(false);
  setAcceptDrops(true); // and you thought this function only installs the menu
}

// Called when we are just about to display the menu
void TQTrayLabel::updateMenu(void)
{
  TQString title = mClass; // + "(" + mTitle + ")";
  mMainMenu->changeItem(mShowId, TQIconSet(*pixmap()),
      TQString((mWithdrawn ? i18n("Show %1") : i18n("Hide %1")).arg(title)));
}

// Session Management
bool TQTrayLabel::saveState(TDEConfig *config)
{
  TRACE("%s saving state", me());

  if (!mDockWhenRestored->isChecked())
  {
    return false;
  }

  config->writeEntry("Application", mProgName.join(" "));
  config->writeEntry("BalloonTimeout", mBalloonTimeout);
  config->writeEntry("CustomIcon", mCustomIcon);
  config->writeEntry("DockWhenFocusLost", mDockWhenFocusLost->isChecked());
  config->writeEntry("DockWhenMinimized", mDockWhenMinimized->isChecked());
  config->writeEntry("DockWhenObscured", mDockWhenObscured->isChecked());
  config->writeEntry("SkipTaskbar", mSkipTaskbar->isChecked());
  config->writeEntry("Withdraw", mWithdrawn);
  return true;
}

bool TQTrayLabel::restoreState(TDEConfig *config)
{
  TRACE("%s restoring state", me());
  setBalloonTimeout(config->readNumEntry("BalloonTimeout", 4000));
  mCustomIcon = config->readEntry("CustomIcon", TQString::null);
  mDockWhenFocusLost->setChecked(config->readBoolEntry("DockWhenFocusLost", false));
  mDockWhenMinimized->setChecked(config->readBoolEntry("DockWhenMinimized", true));
  mDockWhenObscured->setChecked(config->readBoolEntry("DockWhenObscured", false));
  mSkipTaskbar->setChecked(config->readBoolEntry("SkipTaskbar", false));
  mWithdrawn = config->readBoolEntry("Withdraw", false);

  dock();
  scanClients();  // Grab window
  if (mDockedWindow == None)
  {
    return false;
  }
  if (mWithdrawn)
  {
    withdraw();
  }
  else
  {
    map();
  }
  return true;
}

// End kicking butt


#include "tqtraylabel.moc"
