/* WidgetSet category mplementation of View class for X/Motif
 *
 * Copyright (C)  1994, 1995  The Board of Trustees of  
 * The Leland Stanford Junior University.  All Rights Reserved.
 *
 * Authors: Scott Francis, Paul Kunz, Tom Pavel, 
 *	    Imran Qureshi, and Libing Wang (SLAC)
 *	    Adam Fedor (U Colorado)
 *
 * This file is part of an Objective-C class library for X/Motif
 *
 * xmView.m,v 1.36 1995/12/13 22:33:31 fedor Exp
 */

 
#include "View.h"
#include "xtResponder.h"

/* Required for implementation: */
#include <objc/List.h>
#include "appkit/Motif.h"
#include "Application.h"
#include "Window.h"

#define String X11String
#include <Xm/BulletinB.h>  /* for xmScrolledWindowWidgetClass */
#include <Xm/DrawingA.h>
#include <X11/StringDefs.h>	/* For non-Motif resource names */
#undef String

#include "dpsclient/XtGState.h"
#include "dpsclient/XtDrawContext.h"
#include "dpsclient/xwfriends.h"
#include "dpsclient/psops.h"
#include "dpsclient/CGTMatrix.h"

char *ViewInstanceName(void)
{
    return "View";
}


/* The contentView of a Window has this callback enabled. 
   If the window is NONRETAINED, we send a windowDidExpose to the window
   delegate (if it responds), otherwise we try to handle the expose event 
   ourselves (i.e. if we're doing backing store).
*/
static void
_window_did_expose_callback(Widget w, 
			    XtPointer client_data,
			    XtPointer call_data)
{
    View 	*viewid 	    = (View *) client_data;
    XmDrawingAreaCallbackStruct *cb = (XmDrawingAreaCallbackStruct*)call_data;
    Window	*windowid;
    NXEvent 	nxevent;
    XEvent	*ev;
    
    if ( cb->reason != XmCR_EXPOSE ) {
	return;
    }
    ev = cb->event;
    if (ev->type != Expose) {
	return;
    }
  /* Until we can calculate the exposted rectangles correctly,
   * will drop all the expose events except last one and flush
   * the whole window -- Paul
   */
    if ( ![viewid isLastExposeEvent:ev] ) {
	return;
    }
    [viewid makeNXEvent:&nxevent fromXEvent:ev];
    windowid = [viewid window];
    if ([windowid backingType] == NX_NONRETAINED )
	[windowid windowExposed:&nxevent];
    else if ([windowid windowNum])
	[windowid perform:@selector(_windowExposed:) with:(void *)&nxevent];
    
    return;
}

@interface View(WidgetSet)
- _setFrame:(const NXRect *)aFrame;
@end

@implementation View(WidgetSet)

- _moveTo:(NXCoord)x :(NXCoord)y
{
    if ( widgetid ) {
        XtMoveWidget(widgetid, x, y );
    }
    return self;
}

- _setAdjustedFrame:(const NXRect *)aFrame
{
  /* This method sets the position of the view in the X-Window
   * coordinate system.   It should overriden by subclasses whenever
   * they need to adjust the size due to their contents
   */

    [self _setArg:XmNx      to:aFrame->origin.x];
    [self _setArg:XmNy      to:aFrame->origin.y];
    [self _setArg:XmNwidth  to:aFrame->size.width];
    [self _setArg:XmNheight to:aFrame->size.height];
    return self;
}

- _sizeTo:(const NXCoord)width :(const NXCoord)height
{
    [self _setArg:XmNwidth  to:width];
    [self _setArg:XmNheight to:height];
    return self;
}

- _init
{
  /* This method is invoked from from the -initFrame and -awake
   * methods.  Subclasses of View may override this method to 
   * add to their initialization.  In particular, any subclass
   * that doesn't want a xmDrawingArea widget must override this 
   * method.
   */

    classname = xmDrawingAreaWidgetClass;
    [self _setFrame:&frame];
    [self _addArg:XmNallowOverlap:True];
    [self _addArg:XmNnoResize:True];
    [self _addArg:XmNresizePolicy :XmRESIZE_NONE];
    [self _addArg:XmNmarginWidth :0];
    [self _addArg:XmNmarginHeight :0];

/*
    [self _setArg:XmNforeground 
	to:xtGrayToPixel((XMotifContext)[NXApp context], 0.0)];
    [self _setArg:XmNbackground 
	to:xtGrayToPixel((XMotifContext)[NXApp context], 0.6666)];
*/
    return self;
}

- _setAsBulletinBoard
{
  /* This change of class name is used by Box contentView and
   * by the ScrollView subclass.
   */

    classname = xmBulletinBoardWidgetClass;
    return self;
}

- _getFrame
{
    [self _getArg:XmNx      into:(XtArgVal *)&frame.origin.x];
    [self _getArg:XmNy      into:(XtArgVal *)&frame.origin.y];
    [self _getArg:XmNwidth  into:(XtArgVal *)&frame.size.width];
    [self _getArg:XmNheight into:(XtArgVal *)&frame.size.height];
    if ( superview ) {
	NXRect rect;
	[superview getFrame:&rect];
	frame.origin.y = rect.size.height - frame.origin.y - frame.size.height;
    }
    return self;
}

- _addCallback
{
    if ( !superview && window ) {
        XtAddCallback(widgetid, XmNexposeCallback, 
		      _window_did_expose_callback, self);
    }
    return self;
}


- _managedBy:parent wid:(void*)widget
{    
    if (!classname)
       fprintf(stderr, "%s: classname unknown\n", [self name]);
    if (widgetid) {
        XtManageChild(widgetid);
    } else {
	widgetid = XtCreateManagedWidget(instancename, classname,
				         widget, [self _arglist], 
					 [self _numargs]);
	[self _addCallback];
    }
    [self _setnumargs:0];
    if ( subviews ) {
	[self _manageChildren];
    }
    return self;
}
 
- _unManage
{
    if ( widgetid ) {
	XtUnmanageChild(widgetid);
    }
    return self;
}
- _destroy
{
    if ( widgetid ) {
        XtDestroyWidget(widgetid);
    }
    widgetid = NULL;
    if (nil != subviews)  [subviews makeObjectsPerform:@selector(_destroy)];

    return self;
}

/* Routines to handle gstates.  We need to create a gstate for every view
   in X-Windows, because every view draws under a different widget.
   However, a separate X11 Graphic Context (GC) is not created for
   every view unless one is specifically asked for via the allocGState
   method.  Otherwise, the X11 GC is just a pointer to the superview's
   GC. This causes changes to the X11 GC to propogate up the chain.
*/
- _keepGState: (int)gst
{
    _gstate = gst;
    _vFlags.wantsGState = NO;
    vFlags.validGState = YES;
    return self;
}

- _displayFreeGState
{
    return self;
}

- _displayPreLockFocus
{
    return self;
}
  
/* Update the current gstate to reflect our new information */
- _displayLockFocus
{
    // Install our own gstate if we have one, otherwise create one
    if (vFlags.validGState) 
      {
	PSsetgstate(_gstate);
      } 
    else 
      {
	if (superview == nil || _vFlags.wantsGState) 
	  {
	    PSgstateview(self);
	    PSgetint(&_gstate);
	    PSwindowdeviceview([window windowNum], self);
	    [self _keepGState:_gstate];
	    if (_vFlags.notifyToInitGState == YES)
		[self initGState];
	  }
	else
	  PSgsaveview(self);

	/* Update the coordinate system.  Need to translate to our view
	   frame only if we are drawing to backing store. */
	if ([window backingType] != NX_NONRETAINED)
	  PStranslate(NX_X(&frame), NX_Y(&frame));
	NXRectClip(&bounds);
	if (_frameMatrix)
	  PSconcat(cgtToMatrix([_frameMatrix currentMatrix]));
	if (_drawMatrix)
	  PSconcat(cgtToMatrix([_drawMatrix currentMatrix]));
      }

    return self;
}

- _displayUnlockFocus
{
    PSgrestore();
    return self;
}

- _flushView
{
  /* Because only Views with custom drawing draw to the backing store,
   * we need this method to flush the backing store to the widget's 
   * window.   When all drawing is done to the backing store, this
   * method will obsolete.  -- Paul Kunz
   */
    XtGState  *gstate;

    if (vFlags.validGState) {
	PSgstateobject(_gstate, (void **)&gstate);
	if ( gstate && [gstate didDraw] ) {
	    [self lockFocus];
	    [self setNeedsDisplay:YES];
	    [self unlockFocus];
	}
    }
    [subviews makeObjectsPerform:@selector(_flushView)];
    return self;
}

/* these last three methods set x resources, and should be methods only known
   to the category extension.  Box makes use of all of these methods in
   its content view.
 */
- _setResize:(void *)policy
{
    [self _setArg:XmNresizePolicy  to:(XtArgVal)policy];
    return self;
}

- _setOffsets:(NXSize *)offsets
{
    [self _setArg:XmNmarginWidth   to:offsets->width];
    [self _setArg:XmNmarginHeight  to:offsets->height];
    return self;
}

- _setBorderShadow:(void *)shadow andThickness:(void *)thickness
{
    [self _setArg:XmNshadowType      to:(XtArgVal)shadow];
    [self _setArg:XmNshadowThickness to:(XtArgVal)thickness];
    return self;
}

- _setFrame:(const NXRect *)aFrame
{
    NXRect	xframe = *aFrame;

    if ( superview ) {
	NXRect rect;
	[superview getFrame:&rect];
	xframe.origin.y = rect.size.height 
			  - aFrame->origin.y - aFrame->size.height;
    }
    [self _setAdjustedFrame:&xframe];
    return self;
}

- _setNeedsDisplay:(BOOL)flag
{
    XtGState *gstate;

    if (vFlags.validGState) {
	PSgstateobject(_gstate, (void **)&gstate);
	[gstate setNeedsDisplay:flag];
    }
    return self;
}
@end
