/*

  kxt.cpp  -  Xt enabled Qt classed (derived from Qt Extension QXt)

  Copyright (c) 2000,2001 Stefan Schimanski <1Stein@gmx.de>

  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 General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

*/

/****************************************************************************
** $Id: kxt.cpp,v 1.17 2002/08/22 16:39:59 knoll Exp $
**
** Implementation of Qt extension classes for Xt/Motif support.
**
** Created : 980107
**
** Copyright (C) 1992-2000 Troll Tech AS.  All rights reserved.
**
** This file is part of the Qt GUI Toolkit.
**
** This file may be distributed under the terms of the Q Public License
** as defined by Troll Tech AS of Norway and appearing in the file
** LICENSE.QPL included in the packaging of this file.
**
** Licensees holding valid Qt Professional Edition licenses may use this
** file in accordance with the Qt Professional Edition License Agreement
** provided with the Qt Professional Edition.
**
** See http://www.trolltech.com/pricing.html or email sales@trolltech.com for
** information about the Professional Edition licensing, or see
** http://www.trolltech.com/qpl/ for QPL licensing information.
**
*****************************************************************************/

#include <qglobal.h>
#if QT_VERSION < 0x030100

#include <kapplication.h>
#include <qwidget.h>
#include <qobjectlist.h>
#include <qwidgetlist.h>
#include <kdebug.h>
#include <qtimer.h>

#include "kxt.h"

#include <stdio.h>
#include <string.h>
#include <time.h>
#include <limits.h>

#include <X11/Xlib.h>
#include <X11/Intrinsic.h>
#include <X11/IntrinsicP.h> // for XtCreateWindow
#include <X11/Shell.h>
#include <X11/StringDefs.h>
#include <X11/Xutil.h>
#include <X11/Xos.h>

const int XKeyPress = KeyPress;
const int XKeyRelease = KeyRelease;
#undef KeyPress
#undef KeyRelease

extern Atom qt_wm_state;

//#define HAVE_MOTIF
#ifdef HAVE_MOTIF
#include <Xm/Xm.h>
#endif

typedef void (*SameAsXtTimerCallbackProc)(void*,void*);
typedef void (*IntervalSetter)(int);
typedef void (*ForeignEventProc)(XEvent*);

extern XtEventDispatchProc
 qt_np_cascade_event_handler[LASTEvent];      // defined in qnpsupport.cpp
void            qt_reset_color_avail();       // defined in qcolor_x11.cpp
int             qt_activate_timers();         // defined in qapplication_x11.cpp
timeval        *qt_wait_timer();              // defined in qapplication_x11.cpp
void            qt_x11SendPostedEvents();     // defined in qapplication_x11.cpp
int     qt_event_handler( XEvent* event );    // defined in qnpsupport.cpp
extern int      qt_np_count;                  // defined in qnpsupport.cpp
void qt_np_timeout( void* p, void* id );      // defined in qnpsupport.cpp
void qt_np_add_timeoutcb(
        SameAsXtTimerCallbackProc cb );       // defined in qnpsupport.cpp
void qt_np_remove_timeoutcb(
        SameAsXtTimerCallbackProc cb );       // defined in qnpsupport.cpp
void qt_np_add_timer_setter(
        IntervalSetter is );                  // defined in qnpsupport.cpp
void qt_np_remove_timer_setter(
        IntervalSetter is );                  // defined in qnpsupport.cpp
extern XtIntervalId qt_np_timerid;            // defined in qnpsupport.cpp
extern void (*qt_np_leave_cb)
              (XLeaveWindowEvent*);           // defined in qnpsupport.cpp
void qt_np_add_event_proc(
            ForeignEventProc fep );           // defined in qnpsupport.cpp
void qt_np_remove_event_proc(
            ForeignEventProc fep );           // defined in qnpsupport.cpp


typedef struct {
    int empty;
} QWidgetClassPart;

typedef struct _QWidgetClassRec {
    CoreClassPart       core_class;
    QWidgetClassPart    qwidget_class;
} QWidgetClassRec;

//static QWidgetClassRec qwidgetClassRec;

typedef struct {
    /* resources */
    /* (none) */
    /* private state */
    KXtWidget* qxtwidget;
} QWidgetPart;

typedef struct _QWidgetRec {
    CorePart    core;
    QWidgetPart qwidget;
} QWidgetRec;


static
void reparentChildrenOf(QWidget* parent)
{

    if ( !parent->children() )
        return; // nothing to do

    for ( QObjectListIt it( *parent->children() ); it.current(); ++it ) {
        if ( it.current()->isWidgetType() ) {
            QWidget* widget = (QWidget*)it.current();
            XReparentWindow( qt_xdisplay(),
                             widget->winId(),
                             parent->winId(),
                             widget->x(),
                             widget->y() );
            if ( widget->isVisible() )
                XMapWindow( qt_xdisplay(), widget->winId() );
        }
    }

}

void qwidget_realize(
        Widget                widget,
        XtValueMask*          mask,
        XSetWindowAttributes* attributes
    )
{
    widgetClassRec.core_class.realize(widget, mask, attributes);
    KXtWidget* qxtw = ((QWidgetRec*)widget)->qwidget.qxtwidget;
    if (XtWindow(widget) != qxtw->winId()) {
        qxtw->create(XtWindow(widget), FALSE, FALSE);
        reparentChildrenOf(qxtw);
    }
    qxtw->show();
    XMapWindow( qt_xdisplay(), qxtw->winId() );
}

static
QWidgetClassRec qwidgetClassRec = {
  { /* core fields */
    /* superclass               */      (WidgetClass) &widgetClassRec,
    /* class_name               */      (char*)"QWidget",
    /* widget_size              */      sizeof(QWidgetRec),
    /* class_initialize         */      0,
    /* class_part_initialize    */      0,
    /* class_inited             */      FALSE,
    /* initialize               */      0,
    /* initialize_hook          */      0,
    /* realize                  */      qwidget_realize,
    /* actions                  */      0,
    /* num_actions              */      0,
    /* resources                */      0,
    /* num_resources            */      0,
    /* xrm_class                */      NULLQUARK,
    /* compress_motion          */      TRUE,
    /* compress_exposure        */      TRUE,
    /* compress_enterleave      */      TRUE,
    /* visible_interest         */      FALSE,
    /* destroy                  */      0,
    /* resize                   */      XtInheritResize,
    /* expose                   */      XtInheritExpose,
    /* set_values               */      0,
    /* set_values_hook          */      0,
    /* set_values_almost        */      XtInheritSetValuesAlmost,
    /* get_values_hook          */      0,
    /* accept_focus             */      XtInheritAcceptFocus,
    /* version                  */      XtVersion,
    /* callback_private         */      0,
    /* tm_table                 */      XtInheritTranslations,
    /* query_geometry           */      XtInheritQueryGeometry,
    /* display_accelerator      */      XtInheritDisplayAccelerator,
    /* extension                */      0
  },
  { /* qwidget fields */
    /* empty                    */      0
  }
};
static WidgetClass qWidgetClass = (WidgetClass)&qwidgetClassRec;

static bool filters_installed = FALSE;
static KXtApplication* qxtapp = 0;
static XtAppContext appcon;

static
Boolean qt_event_handler_wrapper( XEvent* event )
{
  return (Boolean)qt_event_handler( event );
}

static
void installXtEventFilters()
{
    if (filters_installed) return;
    // Get Xt out of our face - install filter on every event type
    for (int et=2; et < LASTEvent; et++) {
        qt_np_cascade_event_handler[et] = XtSetEventDispatcher(
            qt_xdisplay(), et, qt_event_handler_wrapper );
    }
    filters_installed = TRUE;
}

static
void removeXtEventFilters()
{
    if (!filters_installed) return;
    // We aren't needed any more... slink back into the shadows.
    for (int et=2; et < LASTEvent; et++) {
        XtSetEventDispatcher(
            qt_xdisplay(), et, qt_np_cascade_event_handler[et] );
    }
    filters_installed = FALSE;
}

// When we are in an event loop of QApplication rather than the browser's
// event loop (eg. for a modal dialog), we still send events to Xt.
static
void np_event_proc( XEvent* e )
{
    Widget xtw = XtWindowToWidget( e->xany.display, e->xany.window );
    if ( xtw && qApp->loopLevel() > 0 ) {
        // Allow Xt to process the event
        qt_np_cascade_event_handler[e->type]( e );
    }
}

static void np_set_timer( int interval )
{
    // Ensure we only have one timeout in progress - QApplication is
    // computing the one amount of time we need to wait.
    if ( qt_np_timerid ) {
        XtRemoveTimeOut( qt_np_timerid );
    }
    qt_np_timerid = XtAppAddTimeOut(appcon, interval,
        (XtTimerCallbackProc)qt_np_timeout, 0);
}

static void np_do_timers( void*, void* )
{
    qt_np_timerid = 0; // It's us, and we just expired, that's why we are here.

    qt_activate_timers();

    timeval *tm = qt_wait_timer();

    if (tm) {
        int interval = QMIN(tm->tv_sec,INT_MAX/1000)*1000 + tm->tv_usec/1000;
        np_set_timer( interval );
    }
    qxtapp->sendPostedEvents();
}

/*!
  \class KXtApplication qxt.h
  \brief Allows mixing of Xt/Motif and Qt widgets.

  \extension Xt/Motif

  The KXtApplication and KXtWidget classes allow old Xt or Motif widgets
  to be used in new Qt applications.  They also allow Qt widgets to
  be used in primarily Xt/Motif applications.  The facility is intended
  to aid migration from Xt/Motif to the more comfortable Qt system.
*/

static bool my_xt;

/*!
  Constructs a QApplication and initializes the Xt toolkit.
  The \a appclass, \a options, \a num_options, and \a resources
  arguments are passed on to XtAppSetFallbackResources and
  XtDisplayInitialize.

  Use this constructor when writing a new Qt application which
  needs to use some existing Xt/Motif widgets.
*/
KXtApplication::KXtApplication(int& argc, char** argv,
        const QCString& rAppName, bool allowStyles, bool GUIenabled,
        XrmOptionDescRec *options, int num_options,
        char** resources)
  : KApplication(argc, argv, rAppName, allowStyles, GUIenabled)
{
    my_xt = TRUE;

    XtToolkitInitialize();
    appcon = XtCreateApplicationContext();
    if (resources) XtAppSetFallbackResources(appcon, (char**)resources);
    XtDisplayInitialize(appcon, qt_xdisplay(), name(), rAppName, options,
        num_options, &argc, argv);
    init();
}

/*!
  Constructs a QApplication from the \a display of an already-initialized
  Xt application.

  Use this constructor when introducing Qt widgets into an existing
  Xt/Motif application.
*/
KXtApplication::KXtApplication(Display* dpy, int& argc, char** argv,
        const QCString& rAppName, bool allowStyles, bool GUIenabled)
   : KApplication(dpy, argc, argv, rAppName, allowStyles, GUIenabled)
{
    my_xt = FALSE;
    init();
    appcon = XtDisplayToApplicationContext(dpy);
}

/*!
  Destructs the application.  Does not close the Xt toolkit.
*/
KXtApplication::~KXtApplication()
{
    Q_ASSERT(qxtapp==this);
    removeXtEventFilters();
    qxtapp = 0;

    // the manpage says: "or just exit", that's what we do to avoid
    // double closing of the display
//     if (my_xt) {
//      XtDestroyApplicationContext(appcon);
//      }
}

void KXtApplication::init()
{
    Q_ASSERT(qxtapp==0);
    qxtapp = this;
    installXtEventFilters();
    qt_np_add_timeoutcb(np_do_timers);
    qt_np_add_timer_setter(np_set_timer);
    qt_np_add_event_proc(np_event_proc);
    qt_np_count++;
/*    QTimer *timer = new QTimer( this );
      timer->start(500);*/
}

/*!
  \class KXtWidget qxt.h
  \brief Allows mixing of Xt/Motif and Qt widgets.

  \extension Xt/Motif

  KXtWidget acts as a bridge between Xt and Qt. For utilizing old
  Xt widgets, it can be a QWidget
  based on a Xt widget class. For including Qt widgets in an existing
  Xt/Motif application, it can be a special Xt widget class that is
  a QWidget.  See the constructors for the different behaviors.
*/

void KXtWidget::init(const char* name, WidgetClass widget_class,
                    Widget parent, QWidget* qparent,
                    ArgList args, Cardinal num_args,
                    bool managed)
{
    need_reroot=FALSE;
    xtparent = 0;
    if (parent ) {
        Q_ASSERT(!qparent);
        xtw = XtCreateWidget(name, widget_class, parent, args, num_args);
        if ( widget_class == qWidgetClass )
            ((QWidgetRec*)xtw)->qwidget.qxtwidget = this;
        xtparent = parent;
        if (managed)
            XtManageChild(xtw);
    } else {
        Q_ASSERT(!managed);

        String n, c;
        XtGetApplicationNameAndClass(qt_xdisplay(), &n, &c);
        xtw = XtAppCreateShell(n, c, widget_class, qt_xdisplay(),
                               args, num_args);
        if ( widget_class == qWidgetClass )
            ((QWidgetRec*)xtw)->qwidget.qxtwidget = this;
    }

    if ( qparent ) {
        XtResizeWidget( xtw, 100, 100, 0 );
        XtSetMappedWhenManaged(xtw, False);
        XtRealizeWidget(xtw);
        XSync(qt_xdisplay(), False);    // I want all windows to be created now
        XReparentWindow(qt_xdisplay(), XtWindow(xtw), qparent->winId(), x(), y());
        XtSetMappedWhenManaged(xtw, True);
        need_reroot=TRUE;
    }

    Arg reqargs[20];
    Cardinal nargs=0;
    XtSetArg(reqargs[nargs], XtNx, x());        nargs++;
    XtSetArg(reqargs[nargs], XtNy, y());        nargs++;
    XtSetArg(reqargs[nargs], XtNwidth, width());        nargs++;
    XtSetArg(reqargs[nargs], XtNheight, height());      nargs++;
    //XtSetArg(reqargs[nargs], "mappedWhenManaged", False);     nargs++;
    XtSetValues(xtw, reqargs, nargs);

    //#### destroy();   MLK

    if (!parent || XtIsRealized(parent))
        XtRealizeWidget(xtw);
}


/*!
  Constructs a KXtWidget of the special Xt widget class known as
  "QWidget" to the resource manager.

  Use this constructor to utilize Qt widgets in an Xt/Motif
  application.  The KXtWidget is a QWidget, so you can create
  subwidgets, layouts, etc. using Qt functionality.
*/
KXtWidget::KXtWidget(const char* name, Widget parent, bool managed) :
    QWidget( 0, name, WResizeNoErase )
{
    init(name, qWidgetClass, parent, 0, 0, 0, managed);
    Arg reqargs[20];
    Cardinal nargs=0;
    XtSetArg(reqargs[nargs], XtNborderWidth, 0);
    nargs++;
    XtSetValues(xtw, reqargs, nargs);
}

/*!
  Constructs a KXtWidget of the given \a widget_class.

  Use this constructor to utilize Xt or Motif widgets in a Qt
  application.  The KXtWidget looks and behaves
  like the Xt class, but can be used like any QWidget.

  Note that Xt requires that the most toplevel Xt widget is a shell.
  That means, if \a parent is a KXtWidget, the \a widget_class can be
  of any kind. If there isn't a parent or the parent is just a normal
  QWidget, \a widget_class should be something like \c
  topLevelShellWidgetClass.

  If the \a managed parameter is TRUE and \a parent in not NULL,
  XtManageChild it used to manage the child.
*/
KXtWidget::KXtWidget(const char* name, WidgetClass widget_class,
                     QWidget *parent, ArgList args, Cardinal num_args,
                     bool managed) :
    QWidget( parent, name, WResizeNoErase )
{
    if ( !parent )
        init(name, widget_class, 0, 0, args, num_args, managed);
    else if ( parent->inherits("KXtWidget") )
        init(name, widget_class, ( (KXtWidget*)parent)->xtw , 0, args, num_args, managed);
    else
        init(name, widget_class, 0, parent, args, num_args, managed);
    create(XtWindow(xtw), FALSE, FALSE);
}

/*!
  Destructs the KXtWidget.
*/
KXtWidget::~KXtWidget()
{
    // Delete children first, as Xt will destroy their windows
    //
    QObjectList* list = queryList("QWidget", 0, FALSE, FALSE);
    if ( list ) {
        QWidget* c;
        QObjectListIt it( *list );
        while ( (c = (QWidget*)it.current()) ) {
            delete c;
            ++it;
        }
        delete list;
    }

    if ( need_reroot ) {
        hide();
        XReparentWindow(qt_xdisplay(), winId(), qApp->desktop()->winId(),
            x(), y());
    }

    XtDestroyWidget(xtw);
    destroy( FALSE, FALSE );
}

/*!
  \fn Widget KXtWidget::xtWidget() const
  Returns the Xt widget equivalent for the Qt widget.
*/



/*!
  Reimplemented to produce the Xt effect of getting focus when the
  mouse enters the widget. <em>This may be changed.</em>
*/
bool KXtWidget::x11Event( XEvent * e )
{
    if ( e->type == EnterNotify ) {
        if  ( xtparent )
            setActiveWindow();
    }
    return QWidget::x11Event( e );
}


/*!
  Implement a degree of focus handling for Xt widgets.
*/
void KXtWidget::setActiveWindow()
{
    if  ( xtparent ) {
        if ( !QWidget::isActiveWindow() && isActiveWindow() ) {
            XFocusChangeEvent e;
            e.type = FocusIn;
            e.window = winId();
            e.mode = NotifyNormal;
            e.detail = NotifyInferior;
            XSendEvent( qt_xdisplay(), e.window, TRUE, NoEventMask, (XEvent*)&e );
        }
    } else {
        QWidget::setActiveWindow();
    }
}

/*!
  Different from QWidget::isActiveWindow()
 */
bool KXtWidget::isActiveWindow() const
{
    Window win;
    int revert;
    XGetInputFocus( qt_xdisplay(), &win, &revert );

    if ( win == None) return FALSE;

    QWidget *w = find( (WId)win );
    if ( w ) {
        // We know that window
        return w->topLevelWidget() == topLevelWidget();
    } else {
        // Window still may be a parent (if top-level is foreign window)
        Window root, parent;
        Window cursor = winId();
        Window *ch;
        unsigned int nch;
        while ( XQueryTree(qt_xdisplay(), cursor, &root, &parent, &ch, &nch) ) {
            if (ch) XFree( (char*)ch);
            if ( parent == win ) return TRUE;
            if ( parent == root ) return FALSE;
            cursor = parent;
        }
        return FALSE;
    }
}

/*!\reimp
 */
void KXtWidget::moveEvent( QMoveEvent* )
{
    if ( xtparent )
        return;
    XConfigureEvent c;
    c.type = ConfigureNotify;
    c.event = winId();
    c.window = winId();
    c.x = x();
    c.y = y();
    c.width = width();
    c.height = height();
    c.border_width = 0;
    XSendEvent( qt_xdisplay(), c.event, TRUE, NoEventMask, (XEvent*)&c );
    XtMoveWidget( xtw, x(), y() );
}

/*!\reimp
 */
void KXtWidget::resizeEvent( QResizeEvent* )
{
    if ( xtparent )
        return;
    XtWidgetGeometry preferred;
    (void ) XtQueryGeometry( xtw, 0, &preferred );
    XConfigureEvent c;
    c.type = ConfigureNotify;
    c.event = winId();
    c.window = winId();
    c.x = x();
    c.y = y();
    c.width = width();
    c.height = height();
    c.border_width = 0;
    XSendEvent( qt_xdisplay(), c.event, TRUE, NoEventMask, (XEvent*)&c );
    XtResizeWidget( xtw, width(), height(), preferred.border_width );
}

#include "kxt.moc"

#endif
