/*
 * Copyright (c) 2004 Nokia. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the
 * distribution.
 *
 * Neither the name of Nokia nor the names of its contributors may be
 * used to endorse or promote products derived from this software
 * without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <gtk/gtk.h>

#include "KWQWidget.h"

#include "KWQKHTMLPart.h"
#include "KWQLogging.h"
#include "KWQWindowWidget.h"
#include "WebCoreBridge.h"

#include "khtmlview.h"
#include "render_canvas.h"
#include "render_replaced.h"
#include "render_style.h"

#include "KWQEvent.h"
#include "KWQNamespace.h"
#include "KWQApplication.h"

#include "GObjectMisc.h"

/** callbacks */
extern "C" {
static gboolean focus_inout(GtkWidget *widget, GdkEventFocus *event, gpointer data);
static gboolean key_press_or_release(GtkWidget *widget, GdkEventKey *event, gpointer data);

}


using khtml::RenderWidget;


class QWidgetPrivate
{

public:
    QWidgetPrivate()
	: geom(QRect(0,0,0,0)) {}

    QStyle *style;
    QFont font;
    QPalette pal;
    GPtr<GtkWidget> safe_widget;
    bool visible;

    QWidget* parent;
    QRect geom;
    QCursor currentCursor;

};


QWidget::QWidget(QWidget * parent, const char * name, int f) 
    : data(new QWidgetPrivate)
    ,_widget(0)
{
    QOBJECT_TYPE(QWidget);

    static QStyle defaultStyle;
    data->style = &defaultStyle;
    data->parent = parent;
    data->visible = true;
}

QWidget::QWidget(GtkWidget* awidget)
    : data(new QWidgetPrivate)
    ,_widget(0)
{
    QOBJECT_TYPE(QWidget);

    static QStyle defaultStyle;
    data->style = &defaultStyle;
    data->visible = true;

    setGtkWidget(awidget);
}


QWidget::~QWidget() 
{
    if (_widget) {
	// window of widget
	if (_widget->window) 
	    gdk_window_set_cursor(_widget->window, NULL);

	data->safe_widget = 0; // unrefs etc
    }
    
    delete data;
}

QSize QWidget::sizeHint() const 
{
    // May be overriden by subclasses.
    ASSERT(_widget);
    GtkRequisition r;
    gtk_widget_set_size_request(_widget, -1, -1);
    gtk_widget_size_request(_widget, &r);
    return QSize(r.width, r.height);
}

void QWidget::resize(int w, int h) 
{
    ASSERT(_widget);
    LOG(KwiqLog, "this:%x size:%d,%d", (int)this, w, h);
    QSize s(size());
    if (s.width()!=w || s.height()!=h)
	gtk_widget_set_size_request(_widget, w, h);
}

void QWidget::setActiveWindow() 
{
    KWQKHTMLPart::bridgeForWidget(this)->focusWindow();
}

void QWidget::setEnabled(bool enabled)
{
    gtk_widget_set_sensitive(_widget, enabled);
}

bool QWidget::isEnabled() const
{
    return GTK_WIDGET_IS_SENSITIVE(_widget);
}

long QWidget::winId() const
{
    return (long)this;
}

int QWidget::x() const
{
    ASSERT(_widget);
    return  _widget->allocation.x;
}

int QWidget::y() const 
{
    ASSERT(_widget);
    return _widget->allocation.y;
}

int QWidget::width() const 
{ 
    ASSERT(_widget);
    return _widget->allocation.width;
}

int QWidget::height() const 
{
    ASSERT(_widget);
    return _widget->allocation.height;
}

QSize QWidget::size() const 
{
    return QSize(_widget->allocation.width, _widget->allocation.height);
}

void QWidget::resize(const QSize &s) 
{
    resize(s.width(), s.height());
}

QPoint QWidget::pos() const 
{
    return QPoint(_widget->allocation.x, _widget->allocation.y);
}

void QWidget::move(int x, int y) 
{
    LOG(KwiqLog, "this:%x, _widget:%x, size:%d,%d",(int)this, (int)_widget, x, y);
    ASSERT(_widget);

    QPoint p(pos());
    if (p.x()!=x || p.y()!=y) {
	GtkLayout *layout = GTK_LAYOUT (gtk_widget_get_parent(_widget));
	if (layout) {
	    gtk_layout_move(layout, _widget, x, y);
	} else {
	    GtkContainer *parent = GTK_CONTAINER (gtk_widget_get_parent(_widget));	
	    if (parent) {
		GValue gval= {0,};
		g_value_init(&gval, G_TYPE_INT);
		g_value_set_int(&gval, x);
		gtk_container_child_set_property(parent, _widget, "x", &gval);  
		g_value_set_int(&gval, y);
		gtk_container_child_set_property(parent, _widget, "y", &gval);  
		g_value_unset(&gval);
	    }
	}
    }

}

void QWidget::move(const QPoint &p) 
{
    move(p.x(), p.y());
}

QRect QWidget::frameGeometry() const
{
    if (!_widget){
	g_warning("widget was zero");
	return QRect(-1,-1,-1,-1);
	ASSERT(_widget);
    }

    return QRect( _widget->allocation.x,
		  _widget->allocation.y,
		  _widget->allocation.width,
		  _widget->allocation.height);
}

void QWidget::setFrameGeometry(const QRect &rect)
{
    LOG(KwiqLog, "this:%x, _widget==0, rect:(%d,%d; %d,%d)",
	(int)this, 
	rect.x(),
	rect.y(),
	rect.width(), 
	rect.height());

    ASSERT(_widget);

    g_object_freeze_notify(G_OBJECT (_widget));
    move(rect.x(), rect.y());
    resize(rect.width(), rect.height());
    g_object_thaw_notify(G_OBJECT (_widget));
}


int QWidget::baselinePosition(int height) const
{
    return (int)((16.0f/20.0f)*height);
}

bool QWidget::hasFocus() const
{    
    if (!_widget) return  false;
    return GTK_WIDGET_HAS_FOCUS (_widget);
}

void QWidget::setFocus()
{
    if (hasFocus()) {
        return;
    }

    // KHTML will call setFocus on us without first putting us in our
    // superview and positioning us. Normally layout computes the position
    // and the drawing process positions the widget. Do both things explicitly.
    RenderWidget *renderWidget = 0;
    QObject *qo = const_cast<QObject *>(eventFilterObject());    
    if (qo && QOBJECT_IS_A(qo, khtml::RenderWidget))
	renderWidget = static_cast<RenderWidget *>(qo);
    int x, y;
    if (renderWidget) {
        if (renderWidget->canvas()->needsLayout()) {
            renderWidget->view()->layout();
        }
        if (renderWidget->absolutePosition(x, y)) {
            renderWidget->view()->addChild(this, x, y);
        }
    }

    if (!_widget)
	return;

    if (GTK_WIDGET_CAN_FOCUS(_widget)) {
        KWQKHTMLPart::bridgeForWidget(this)->makeFirstResponder(_widget);
    }
}

void QWidget::clearFocus()
{
    if (!hasFocus()) {
        return;
    }

    KWQKHTMLPart::clearDocumentFocus(this);
}

bool QWidget::checksDescendantsForFocus() const
{
    return false;
}

QWidget::FocusPolicy QWidget::focusPolicy() const
{
    // This provides support for controlling the widgets that take 
    // part in tab navigation. Widgets must:
    // 1. not be hidden by css
    // 2. be enabled
    // 3. accept first responder

    RenderWidget *widget = const_cast<RenderWidget *>
	(static_cast<const RenderWidget *>(eventFilterObject()));
    if (widget->style()->visibility() != khtml::VISIBLE)
        return NoFocus;

    if (!isEnabled())
        return NoFocus;
#if 0    
    KWQ_BLOCK_EXCEPTIONS;
    if (![getView() acceptsFirstResponder])
        return NoFocus;
    KWQ_UNBLOCK_EXCEPTIONS;
#endif    

    return TabFocus;
}

const QPalette& QWidget::palette() const
{
    return data->pal;
}

void QWidget::setPalette(const QPalette &palette)
{
    data->pal = palette;
}

void QWidget::unsetPalette()
{
    // Only called by RenderFormElement::layout, which I suspect will
    // have to be rewritten.  Do nothing.
}

QStyle &QWidget::style() const
{
    return *data->style;
}

void QWidget::setStyle(QStyle *style)
{
    // According to the Qt implementation 
    /*
      Sets the widget's GUI style to \a style. Ownership of the style
      object is not transferred.
    */
    data->style = style;
}

QFont QWidget::font() const
{
    return data->font;
}

extern "C" {
static void _modify_widget_font(GtkWidget *widget, gpointer data) {
    PangoFontDescription *descr = (PangoFontDescription *)data;
    ASSERT(descr);
    
    gtk_widget_modify_font(widget, descr);
}
}
void QWidget::setFont(const QFont &font)
{
    data->font = font;

    if (_widget) {
	gtk_widget_modify_font(_widget, font.getPangoFont());
	if (GTK_IS_CONTAINER(_widget)) {
	    gtk_container_foreach(GTK_CONTAINER (_widget), _modify_widget_font, font.getPangoFont());
	}
    }
}

void QWidget::constPolish() const
{
}

bool QWidget::isVisible() const
{
    if (!_widget) 
	return false;

    return GTK_WIDGET_VISIBLE(_widget);
}

void QWidget::setCursor(const QCursor &cur)
{
    if (!_widget) return;
    if (_widget->window) {
	gdk_window_set_cursor(_widget->window, cur.handle()); // NULL means parent
	data->currentCursor = cur;
    }
}


QCursor QWidget::cursor()
{
    //x doesn't support this..
    //in KHTMLView::viewPortMouseEvent this is needed for comparing current vs new

    return data->currentCursor;
}

void QWidget::unsetCursor()
{
    setCursor(QCursor());
}

bool QWidget::focusNextPrevChild(bool)
{
    // didn't find anything to focus to. return false, so the event can be
    // handled by the parent (by returning FALSE for focus -signal)
    return false;
}

bool QWidget::hasMouseTracking() const
{
    return true;
}

void QWidget::show()
{
    if (!_widget) return;

    if (!data || isVisible())
        return;

    gtk_widget_show_all( _widget );
}

void QWidget::hide()
{
    
    if (!data || !isVisible())
        return;

    gtk_widget_hide( _widget );
}

QPoint QWidget::mapFromGlobal(const QPoint &p) const
{
#if 0
    NSPoint bp = {0,0};

    KWQ_BLOCK_EXCEPTIONS;
    bp = [[KWQKHTMLPart::bridgeForWidget(this) window] convertScreenToBase:[data->view convertPoint:p toView:nil]];
    KWQ_UNBLOCK_EXCEPTIONS;
#endif
    LOG(NotYetImplemented, "KWIQ");
    return QPoint();
}

void QWidget::setGtkWidget(GtkWidget* widget)
{
    if (widget == _widget) return;
    
    _widget = widget;
    data->safe_widget = widget;
    
    if (_widget) {
	data->safe_widget.connect("focus-in-event",
				  ::focus_inout,
				  this);

	data->safe_widget.connect("focus-out-event",
				  ::focus_inout,
				  this);

	data->safe_widget.connect("key-press-event",
			::key_press_or_release,
			this);

	data->safe_widget.connect("key-release-event",
				  ::key_press_or_release,
				  this);

	if (widget->window)
	    gdk_window_set_cursor(widget->window, NULL);
    }
}

void QWidget::lockDrawingFocus()
{
    LOG(NotYetImplemented, "KWIQ");
}

void QWidget::unlockDrawingFocus()
{
    LOG(NotYetImplemented, "KWIQ");
}

void QWidget::disableFlushDrawing()
{
    LOG(NotYetImplemented, "KWIQ");
}

void QWidget::enableFlushDrawing()
{
    LOG(NotYetImplemented, "KWIQ");
}

void QWidget::setDrawingAlpha(float alpha)
{
    LOG(NotYetImplemented,"KWIQ");
}

void QWidget::paint(QPainter *p, const QRect &r)
{
    if (p->paintingDisabled()) {
        return;
    }

    GdkEventExpose eev;
    eev.type = GDK_EXPOSE;
    eev.window = _widget->window;
    eev.send_event = TRUE;
    eev.count = 0;
    eev.area.x =  r.x();
    eev.area.y =  r.y();
    eev.area.width =  r.width()+1;
    eev.area.height = r.height()+1;
    eev.region = gdk_region_rectangle(&eev.area);
    
    gtk_widget_send_expose(_widget, (GdkEvent*)&eev);
    gdk_region_destroy(eev.region);
}

void QWidget::sendConsumedMouseUp()
{
#if 0    
    khtml::RenderWidget *widget = const_cast<khtml::RenderWidget *>
	(static_cast<const khtml::RenderWidget *>(eventFilterObject()));

    KWQ_BLOCK_EXCEPTIONS;
    widget->sendConsumedMouseUp(QPoint([[NSApp currentEvent] locationInWindow]),
				// FIXME: should send real state and button
				0, 0);
#endif    
    LOG(NotYetImplemented, "KWIQ");
}

void QWidget::setWritingDirection(QPainter::TextDirection direction)
{
    GtkWidget *w = getGtkWidget();
    PangoContext *pc = gtk_widget_get_pango_context( w );
    PangoDirection pdir = pango_context_get_base_dir( pc );    
    
    switch (direction){
    case QPainter::LTR:
    {
        // do nothing as direction is already correct
        if ( pdir == PANGO_DIRECTION_LTR ) return;
        pdir = PANGO_DIRECTION_LTR;
    }
    case QPainter::RTL:
        // do nothing as direction is already correct
        if ( pdir == PANGO_DIRECTION_RTL ) return;
  	pdir = PANGO_DIRECTION_RTL;
    }	
    pango_context_set_base_dir( pc, pdir );
}


extern "C" {

/** @return \TRUE if stops focus in event propagation, \FALSE if propagation continues */
static gboolean
focus_inout(GtkWidget *widget, GdkEventFocus *event, gpointer data)
{
    QWidget *self = static_cast<QWidget*>(data);
    ASSERT(self);

    if (!self->eventFilterObject()) return FALSE;

    if (event->in == TRUE){
        QFocusEvent event(QEvent::FocusIn);
	const_cast<QObject *>(self->eventFilterObject())->eventFilter(self, &event);
    } else {
	QFocusEvent event(QEvent::FocusOut);
        const_cast<QObject *>(self->eventFilterObject())->eventFilter(self, &event);
    }
    return FALSE;
}

/** catches keypresses for widgets that consume keyevents */
static gboolean
key_press_or_release(GtkWidget *widget, GdkEventKey *event, gpointer data)
{
    QWidget* self = static_cast<QWidget*>(data);

    WebCoreBridge* bridge = KWQKHTMLPart::bridgeForWidget(self);
    assert(bridge);
    
    return bridge->interceptKeyEvent(event, false) ? TRUE:FALSE;
}


} /* extern "C" */
