/* $XConsortium: wsb.c,v 5.9 94/04/17 20:42:31 mor Exp $ */

/***********************************************************

Copyright (c) 1989, 1990, 1991  X Consortium

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Except as contained in this notice, the name of the X Consortium shall not be
used in advertising or otherwise to promote the sale, use or other dealings
in this Software without prior written authorization from the X Consortium.

Copyright 1989, 1990, 1991 by Sun Microsystems, Inc. 

                        All Rights Reserved

Permission to use, copy, modify, and distribute this software and its 
documentation for any purpose and without fee is hereby granted, 
provided that the above copyright notice appear in all copies and that
both that copyright notice and this permission notice appear in 
supporting documentation, and that the name of Sun Microsystems,
not be used in advertising or publicity pertaining to distribution of 
the software without specific, written prior permission.  

SUN MICROSYSTEMS DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, 
INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT 
SHALL SUN MICROSYSTEMS BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL 
DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
SOFTWARE.

******************************************************************/

/* PEX/PHIGS workstation utility functions for the B model (client side
 * workstations and structure storage).
 */

#include "phg.h"
#include "cp.h"
#include "ws.h"
#include "css.h"
#include "alloc.h"
#include "PEX.h"
#include "PEXproto.h"
#include "PEXmacs.h"
#include "PEXfuncs.h"
#include "phigspex.h"


static void
wsb_load_funcs( ws )
    Ws 		*ws;
{
    ws->close = phg_wsb_close_ws;
    ws->redraw_all = phg_wsb_redraw_all;
    ws->conditional_redraw = phg_wsb_conditional_redraw;
    ws->repaint_all = phg_wsb_repaint_all;
    ws->make_requested_current = phg_wsb_make_requested_current;
    ws->update = phg_wsb_update;
    ws->set_disp_update_state = phg_wsb_set_disp_update_state;
    ws->set_rep = phg_wsb_set_rep;
    ws->set_filter = phg_wsb_set_filter;
    ws->set_colour_model = (void(*)())NULL;	/* state set by CP */
    ws->set_hlhsr_mode = phg_wsb_set_hlhsr_mode;
    ws->set_view_input_priority = phg_wsb_set_view_input_priority;
    ws->set_ws_window = phg_wsb_set_ws_window;
    ws->set_ws_vp = phg_wsb_set_ws_vp;
    ws->delete_el_for_repl = (void(*)())NULL;	/* not used */
    ws->add_el = phg_wsb_add_el;
    ws->copy_struct = phg_wsb_copy_struct;
    ws->close_struct = phg_wsb_close_struct;
    ws->move_ep = (void(*)())NULL;	/* not used */
    ws->delete_el = phg_wsb_delete_el;
    ws->delete_struct = phg_wsb_delete_struct;
    ws->delete_struct_net = phg_wsb_delete_struct_net;
    ws->delete_all_structs = phg_wsb_delete_all_structs;
    ws->post = phg_wsb_post;
    ws->unpost = phg_wsb_unpost;
    ws->unpost_all = phg_wsb_unpost_all;
    ws->change_posting = phg_wsb_change_posting;
    ws->drawable_pick = phg_wsb_drawable_pick;
    ws->map_points = phg_wsb_map_points;
    ws->redraw_regions = phg_wsb_redraw_regions;

    ws->inq_view_indices = phg_wsb_inq_view_indices;
    ws->inq_bundle_indices = phg_wsx_inq_LUT_indices;
    ws->inq_posted = phg_wsb_inq_posted;
    ws->inq_representation = phg_wsb_inq_rep;
    ws->inq_view_rep = phg_wsb_inq_view_rep;
    ws->inq_ws_xform = phg_wsb_inq_ws_xform;
    ws->inq_disp_update_state = phg_wsb_inq_disp_update_state;
    ws->inq_filter = phg_wsb_inq_filter;
    ws->inq_hlhsr_mode = phg_wsb_inq_hlhsr_mode;
    ws->inq_colour_model = (void(*)())NULL;	/* have CP return it */
}

/* 
 * Tables that determine what update action is valid at a give point
 * in time.  The table has 3 axes:
 *	[Time] [Modification Mode] [Deferral Mode].
 */

static  Ws_action_table default_action_table =
{
    {   /* PHG_TIME_NOW */  
	{   /* NIVE */   
	    PHG_UPDATE_ACCURATE, 		/* ASAP */
	    PHG_UPDATE_IF_IG,	 		/* BNIG */
	    PHG_UPDATE_IF_IL, 			/* BNIL */
	    PHG_UPDATE_NOTHING, 		/* ASTI */
	    PHG_UPDATE_NOTHING			/* WAIT */
	},
	{   /* UWOR */
	    PHG_UPDATE_ACCURATE, 		/* ASAP */
	    PHG_UPDATE_IF_IG, 			/* BNIG */
	    PHG_UPDATE_IF_IL, 			/* BNIL */
	    PHG_UPDATE_UWOR, 			/* ASTI */
	    PHG_UPDATE_UWOR			/* WAIT */
	},
	{   /* UQUM */
	    PHG_UPDATE_ACCURATE, 		/* ASAP */
	    PHG_UPDATE_IF_IG, 			/* BNIG */
	    PHG_UPDATE_IF_IL, 			/* BNIL */
	    PHG_UPDATE_UQUM, 			/* ASTI */
	    PHG_UPDATE_UQUM			/* WAIT */
	}, 
    }, 

    {   /* PHG_TIME_BIG */  
	{   /* NIVE */   
	    ASSURE_CORRECT, 			/* ASAP */
	    PHG_UPDATE_ACCURATE, 		/* BNIG */
	    PHG_UPDATE_NOTHING, 		/* BNIL */
	    PHG_UPDATE_NOTHING, 		/* ASTI */
	    PHG_UPDATE_NOTHING			/* WAIT */
	}, 
	{   /* UWOR */ 
	    ASSURE_CORRECT, 			/* ASAP */
	    PHG_UPDATE_ACCURATE, 		/* BNIG */
	    PHG_UPDATE_NOTHING, 		/* BNIL */
	    PHG_UPDATE_NOTHING, 		/* ASTI */
	    PHG_UPDATE_NOTHING			/* WAIT */
	}, 
	{   /* UQUM */   
	    ASSURE_CORRECT, 			/* ASAP */
	    PHG_UPDATE_ACCURATE, 		/* BNIG */
	    PHG_UPDATE_NOTHING, 		/* BNIL */
	    PHG_UPDATE_NOTHING, 		/* ASTI */
	    PHG_UPDATE_NOTHING			/* WAIT */
	}, 
    }, 	    
    {   /* PHG_TIME_BIL */  
	{   /* NIVE */   
	    ASSURE_CORRECT, 			/* ASAP */
	    PHG_UPDATE_ACCURATE, 		/* BNIG */
	    PHG_UPDATE_ACCURATE, 		/* BNIL */
	    PHG_UPDATE_NOTHING, 		/* ASTI */
	    PHG_UPDATE_NOTHING			/* WAIT */
	}, 
	{   /* UWOR */ 
	    ASSURE_CORRECT, 			/* ASAP */
	    PHG_UPDATE_ACCURATE, 		/* BNIG */
	    PHG_UPDATE_ACCURATE, 		/* BNIL */
	    PHG_UPDATE_NOTHING, 		/* ASTI */
	    PHG_UPDATE_NOTHING			/* WAIT */
	}, 
	{   /* UQUM */   
	    ASSURE_CORRECT, 			/* ASAP */
	    PHG_UPDATE_ACCURATE, 		/* BNIG */
	    PHG_UPDATE_ACCURATE, 		/* BNIL */
	    PHG_UPDATE_NOTHING, 		/* ASTI */
	    PHG_UPDATE_NOTHING			/* WAIT */
	}, 
    }, 
    {   /* PHG_TIME_ATI */  
	{   /* NIVE */   
	    ASSURE_CORRECT, 			/* ASAP */
	    PHG_UPDATE_IF_INCORRECT, 		/* BNIG */
	    PHG_UPDATE_IF_INCORRECT, 		/* BNIL */
	    PHG_UPDATE_ACCURATE, 		/* ASTI */
	    PHG_UPDATE_NOTHING			/* WAIT */
	}, 
	{   /* UWOR */ 
	    ASSURE_CORRECT, 			/* ASAP */
	    PHG_UPDATE_IF_INCORRECT, 		/* BNIG */
	    PHG_UPDATE_IF_INCORRECT, 		/* BNIL */
	    PHG_UPDATE_ACCURATE, 		/* ASTI */
	    PHG_UPDATE_NOTHING			/* WAIT */
	},
	{   /* UQUM */   
	    ASSURE_CORRECT, 			/* ASAP */
	    PHG_UPDATE_IF_INCORRECT, 		/* BNIG */
	    PHG_UPDATE_IF_INCORRECT, 		/* BNIL */
	    PHG_UPDATE_ACCURATE, 		/* ASTI */
	    PHG_UPDATE_NOTHING			/* WAIT */
	},
    }
};


static
void
init_update_state( ws, wst )
    Ws	*ws;
    Wst	*wst;
{   
    register Ws_output_ws	*ows = &ws->out_ws;
    register Wsb_output_ws	*owsb = &ows->model.b;

    ows->def_mode = ws->type->desc_tbl.phigs_dt.out_dt.deferral_mode;
    ows->mod_mode = ws->type->desc_tbl.phigs_dt.out_dt.modification_mode;

    owsb->update_action_table = (Ws_action_table_ptr)default_action_table;

    /* cache action for time "NOW" */
    owsb->now_action = (*owsb->update_action_table)
	[(int)PHG_TIME_NOW][(int)ows->mod_mode][(int)ows->def_mode];

    owsb->vis_rep = PVISUAL_ST_CORRECT;
    owsb->surf_state = PSURF_EMPTY;
}


static int
init_view_table( ws, wst )
    Ws	*ws;
    Wst	*wst;
{   
    Wsb_output_ws	*owsb = &ws->out_ws.model.b;

    register Ws_view_entry	*view;
    register Pview_rep3		*predef_view;
    register int		i, max_pd_view;

    ALLOC_DECLARE(5);

    /* Allocate and initiailize the view table to correspond to the
     * predefined views in the PEX view LUT.  This code puts a limit on the
     * number of view entries that can be defined.  This limit may
     * be less than the server's limit.
     */
    owsb->num_views = ws->type->desc_tbl.phigs_dt.num_view_indices;

    if ( !ALLOCATED( owsb->views = (Ws_view_entry *)
	    malloc((unsigned)(owsb->num_views * sizeof(Ws_view_entry)))) ) {
	ERR_BUF( ws->erh, ERR900 );
	return 0;
    }

    owsb->top_view = 0;
    if ( !ALLOCATED( owsb->view_priorities = (Ws_view_priority *)
	    malloc((unsigned)(owsb->num_views * sizeof(Ws_view_priority)))) ) {
	ERR_BUF( ws->erh, ERR900 );
	ALLOC_FREE;
	return 0;
    }

    owsb->num_pending_views = 0;
    if ( !ALLOCATED( owsb->pending_views = (Ws_pending_view *)
	    malloc((unsigned)(owsb->num_views * sizeof(Ws_pending_view)))) ) {
	ERR_BUF( ws->erh, ERR900 );
	ALLOC_FREE;
	return 0;
    }

    max_pd_view = wst->desc_tbl.phigs_dt.num_predefined_views - 1;
    predef_view = wst->desc_tbl.phigs_dt.default_views;
    view = owsb->views;
    /* Load the predefined views. */
    for ( i = 0; i <= max_pd_view; i++, predef_view++, view++ ) {
	view->pending = PUPD_NOT_PEND;
	bcopy( (char *)predef_view->ori_matrix,(char *) view->vom,
	    sizeof(Pmatrix3) );
	bcopy( (char *)predef_view->map_matrix, (char *)view->vmm,
	    sizeof(Pmatrix3) );
	view->clip_limit = predef_view->clip_limit;
	view->xy_clip = predef_view->xy_clip;
	view->back_clip = predef_view->back_clip;
	view->front_clip = predef_view->front_clip;
	/* view->npc_to_wc not computed until needed in input code. */
	view->npc_to_wc_state = WS_INV_NOT_CURRENT;
    }

    /* Load the available but not predefined views. */
    for ( /* use existing index */ ; i < owsb->num_views; i++, view++ )
	*view = owsb->views[0];
    
    /* Initialize the view transformation priorities.  The list is
     * terminated at top and bottom by -1.
     */
    for ( i = 0; i < owsb->num_views; i++ ) {
	owsb->view_priorities[i].higher = i - 1;
	owsb->view_priorities[i].lower = i + 1;
    }
    owsb->view_priorities[owsb->num_views - 1].lower = -1;
    
    return 1;
}


static int
init_output_state( ws )
    Ws		*ws;
{
    Wst		*wst = ws->type;

    register Wsb_output_ws	*owsb = &ws->out_ws.model.b;

    if ( !init_view_table( ws, wst ) )
	return 0;

    /* Initialize the workstation transform. */
    owsb->req_ws_window.x_min = 0.0;
    owsb->req_ws_window.x_max = 1.0;
    owsb->req_ws_window.y_min = 0.0;
    owsb->req_ws_window.y_max = 1.0;
    owsb->req_ws_window.z_min = 0.0;
    owsb->req_ws_window.z_max = 1.0;
    owsb->ws_window = owsb->req_ws_window;
    owsb->ws_window_pending = PUPD_NOT_PEND;

    owsb->req_ws_viewport.x_min = 0.0;
    owsb->req_ws_viewport.x_max = ws->ws_rect.width;
    owsb->req_ws_viewport.y_min = 0.0;
    owsb->req_ws_viewport.y_max = ws->ws_rect.height;
    owsb->req_ws_viewport.z_min = 0.0;
    owsb->req_ws_viewport.z_max = 1.0;
    owsb->ws_viewport = owsb->req_ws_viewport;
    owsb->ws_viewport_pending = PUPD_NOT_PEND;

    phg_wsx_compute_ws_transform( &owsb->ws_window, &owsb->ws_viewport,
	&owsb->ws_xform );

    /* Initialize the list of posted structs. */
    owsb->posted.lowest.higher = &owsb->posted.highest;
    owsb->posted.lowest.lower = NULL;
    owsb->posted.highest.higher = NULL;
    owsb->posted.highest.lower = &owsb->posted.lowest;

    /* Initialize other miscellaneous output state. */
    owsb->cur_hlhsr_mode = PHIGS_HLHSR_MODE_NONE;
    owsb->req_hlhsr_mode = PHIGS_HLHSR_MODE_NONE;
    owsb->hlhsr_mode_pending = PUPD_NOT_PEND;

    return 1;
}

static int
wsb_init_resources( ws )
    Ws		*ws;
{
    CARD16		count;
    CARD32		size, *rid_list;
    pexTableIndex	start;
    pexBitmask		pmask[PEXMSPipeline], rmask;
    CARD32		*card32_p;
    pexReflectionAttr	*refl_attr, *bf_refl_attr;
    pexFloatColour	*spec_colr, *bf_spec_colr;
    pexViewport		*vp;

    register int		i;
    register Ws_view_entry	*view;
    register pexViewEntry	*pex_view;

    /* Initialize all the predefined table entries from the WDT. */
    if ( !phg_wsx_init_LUTs( ws, 1 ) )
	return 0;

    /* Initialize the Pipeline Context.  Since PEX and PHIGS defaults
     * are different for reflection attrs and culling mode, set those in
     * PEX when creating the context.
     */
    size = 0;
    bzero((char *)pmask, sizeof(pmask));
    PEX_BITSET(pmask,PEXPCSurfaceReflAttr);
	size += sizeof(*refl_attr) + sizeof(*spec_colr);
    refl_attr = (pexReflectionAttr *)ws->scratch.buf;
    spec_colr = (pexFloatColour *)(refl_attr + 1);
    refl_attr->ambient = 1.0;
    refl_attr->diffuse = 1.0;
    refl_attr->specular = 1.0;
    refl_attr->specularConc = 0.0;
    refl_attr->transmission = 0.0;
    refl_attr->specularColour.colourType = PEXRgbFloatColour;
    spec_colr->first = spec_colr->second = spec_colr->third = 1.0;
    PEX_BITSET(pmask,PEXPCBfSurfaceReflAttr);
	size += sizeof(*bf_refl_attr) + sizeof(*bf_spec_colr);
    bf_refl_attr = (pexReflectionAttr *)(spec_colr + 1);
    *bf_refl_attr = *refl_attr;
    bf_spec_colr = (pexFloatColour *)(bf_refl_attr + 1);
    *bf_spec_colr = *spec_colr;
    PEX_BITSET(pmask,PEXPCCullingMode); size += sizeof(CARD32);
    card32_p = (CARD32 *)(bf_spec_colr + 1);
    *card32_p = 0;
    (void)PEXChangePipelineContext( ws->display, ws->out_ws.model.b.pipeline,
	pmask, size, (char *)ws->scratch.buf );

    /* Initialize the renderer. */
    size = 0;
    bzero((char *)&rmask, sizeof(rmask));
    rid_list = (XID *)ws->scratch.buf;
    /* The way the bits are defined in PEX.h doesn't allow the use of
     * PEX_BITSET().
     */
    rmask |= PEXRDPipelineContext; size += sizeof(CARD32);
	*rid_list++ = ws->out_ws.model.b.pipeline;
    rmask |= PEXRDMarkerBundle; size += sizeof(CARD32);
	*rid_list++ = ws->out_ws.lut.marker;
    rmask |= PEXRDTextBundle; size += sizeof(CARD32);
	*rid_list++ = ws->out_ws.lut.text;
    rmask |= PEXRDLineBundle; size += sizeof(CARD32);
	*rid_list++ = ws->out_ws.lut.line;
    rmask |= PEXRDInteriorBundle; size += sizeof(CARD32);
	*rid_list++ = ws->out_ws.lut.interior;
    rmask |= PEXRDEdgeBundle; size += sizeof(CARD32);
	*rid_list++ = ws->out_ws.lut.edge;
    rmask |= PEXRDViewTable; size += sizeof(CARD32);
	*rid_list++ = ws->out_ws.lut.view;
    rmask |= PEXRDColourTable; size += sizeof(CARD32);
	*rid_list++ = ws->out_ws.lut.colour;
    rmask |= PEXRDDepthCueTable; size += sizeof(CARD32);
	*rid_list++ = ws->out_ws.lut.depth_cue;
    rmask |= PEXRDLightTable; size += sizeof(CARD32);
	*rid_list++ = ws->out_ws.lut.light_source;
    rmask |= PEXRDColourApproxTable; size += sizeof(CARD32);
	*rid_list++ = ws->out_ws.lut.colour_approx;
    rmask |= PEXRDPatternTable; size += sizeof(CARD32);
	*rid_list++ = ws->out_ws.lut.pattern;
    rmask |= PEXRDTextFontTable; size += sizeof(CARD32);
	*rid_list++ = ws->out_ws.lut.font;
    rmask |= PEXRDHighlightIncl; size += sizeof(CARD32);
	*rid_list++ = ws->out_ws.nset.hlt_incl;
    rmask |= PEXRDHighlightExcl; size += sizeof(CARD32);
	*rid_list++ = ws->out_ws.nset.hlt_excl;
    rmask |= PEXRDInvisibilityIncl; size += sizeof(CARD32);
	*rid_list++ = ws->out_ws.nset.invis_incl;
    rmask |= PEXRDInvisibilityExcl; size += sizeof(CARD32);
	*rid_list++ = ws->out_ws.nset.invis_excl;
    rmask |= PEXRDHlhsrMode; size += sizeof(CARD32);
	*rid_list++ = (CARD32)PEX_CONV_PHIGS_HLHSR_MODE(PHIGS_HLHSR_MODE_NONE);
    rmask |= PEXRDViewport; size += sizeof(pexViewport);
	vp = (pexViewport *)rid_list++;
	vp->minval.x = ws->out_ws.model.b.req_ws_viewport.x_min;
	vp->minval.y = ws->out_ws.model.b.req_ws_viewport.y_min;
	vp->minval.z = ws->out_ws.model.b.req_ws_viewport.z_min;
	vp->maxval.x = ws->out_ws.model.b.req_ws_viewport.x_max;
	vp->maxval.y = ws->out_ws.model.b.req_ws_viewport.y_max;
	vp->maxval.z = ws->out_ws.model.b.req_ws_viewport.z_max;
	vp->useDrawable = 0;
    /* No need to set the NPC volume since the PEX default is correct. */
    (void)PEXChangeRenderer( ws->display, ws->rid, rmask, size,
	(char *)ws->scratch.buf );

    return 1;
}

static int
wsb_create_resources( ws )
    Ws		*ws;
{
    pexBitmask		pmask[PEXMSPipeline];
    pexBitmask		dyn_tbls, dyn_nsets, dyn_attrs;
    Pint		err;
    CARD16		*card16_p;
    Drawable            draw;
    int                 numbufs, mbuf_event_base, mbuf_error_base;;


    /* Create the LUTs, renderer and pipeline context. */
    if ( !phg_wsx_create_LUTs( ws, 1, &err ) ) {
	ERR_BUF( ws->erh, err );
	return 0;
    }

    /* Create the pipeline context. */
    ws->out_ws.model.b.pipeline = XAllocID(ws->display);
    (void)PEXCreatePipelineContext( ws->display, ws->out_ws.model.b.pipeline,
	(pexBitmask *)NULL, (CARD32)0, (char *)NULL );

    /* Make an inquiry to see if the create worked. */
    /* TODO: Remove when GetPipeline fixed.
    bzero((char *)pmask, sizeof(pmask));
    PEX_BITSET(pmask,PEXPCCullingMode);
    if ( !PEXGetPipelineContext( ws->display, ws->out_ws.model.b.pipeline,
	    pmask, (char **)&card16_p ) ) {
	ERR_BUF( ws->erh, ERRN202 );
	return 0;
    }
    */

    /* Check the Workstation buffer mode and do the double buffer
       create here if necessary This is simpler than on the workstation
       since this PHIGS implementation only lets the Double Buffers
       be created when the workstation is created, so no change action
       need be done
    */

    /* initialize the local drawable variable */
    draw = ws->drawable_id;
    ws->out_ws.model.b.has_double_buffer = FALSE;

    if (ws->type->desc_tbl.xwin_dt.buffer_mode == PHIGS_BUF_DOUBLE) {
      if (XmbufQueryExtension(ws->display, &mbuf_event_base, &mbuf_error_base)){

        /* the Multi-Buffer extension is there */
        numbufs = XmbufCreateBuffers(ws->display, ws->drawable_id, 2,
                     MultibufferUpdateActionBackground,
                     MultibufferUpdateHintFrequent,
                     &ws->out_ws.model.b.double_drawable[0]);
        if  (numbufs == 2) {
          /* got the buffers OK */
          ws->out_ws.model.b.front = 0;
          ws->out_ws.model.b.back = 1;
          ws->out_ws.model.b.has_double_buffer = TRUE;
          draw = ws->out_ws.model.b.double_drawable[1];
          /* this isn't implemented yet
            XmbufClearBufferArea(ws->display, draw, 0, 0, 0, 0, False);
          */
        } else
          /* buffers didn't get created correctly, bag them  */
          XmbufDestroyBuffers(ws->display, ws->drawable_id);
      }
    }


    /* Create the renderer, initializing the necessary attributes. */
    ws->rid = XAllocID(ws->display);
    (void)PEXCreateRenderer( ws->display, ws->rid, draw,
	(pexBitmask)0, (CARD32)0, (char *)NULL );

    /* Make an inquiry to see if the create worked. */
    if ( !PEXGetRendererDynamics( ws->display, ws->rid, &dyn_tbls,
	    &dyn_nsets, &dyn_attrs ) ) {
	ERR_BUF( ws->erh, ERRN202 );
	return 0;
    }

    return 1;
}


static void
wsb_update_wdt( ws )
    Ws		*ws;
{
    /* Update the dynamics flags -- everything IRG for client-side WSs. */
    ws->type->desc_tbl.phigs_dt.out_dt.view_rep = PDYN_IRG;
    ws->type->desc_tbl.phigs_dt.out_dt.polyline_bundle_rep = PDYN_IRG;
    ws->type->desc_tbl.phigs_dt.out_dt.polymarker_bundle_rep = PDYN_IRG;
    ws->type->desc_tbl.phigs_dt.out_dt.text_bundle_rep = PDYN_IRG;
    ws->type->desc_tbl.phigs_dt.out_dt.interior_bundle_rep = PDYN_IRG;
    ws->type->desc_tbl.phigs_dt.out_dt.edge_bundle_rep = PDYN_IRG;
    ws->type->desc_tbl.phigs_dt.out_dt.pattern_rep = PDYN_IRG;
    ws->type->desc_tbl.phigs_dt.out_dt.colour_rep = PDYN_IRG;
    ws->type->desc_tbl.phigs_dt.out_dt.ws_xform = PDYN_IRG;
    ws->type->desc_tbl.phigs_dt.out_dt.highlight_filter = PDYN_IRG;
    ws->type->desc_tbl.phigs_dt.out_dt.invis_filter = PDYN_IRG;
    ws->type->desc_tbl.phigs_dt.out_dt.hlhsr_mode = PDYN_IRG;
    ws->type->desc_tbl.phigs_dt.out_dt.struct_content_mod = PDYN_IRG;
    ws->type->desc_tbl.phigs_dt.out_dt.post = PDYN_IRG;
    ws->type->desc_tbl.phigs_dt.out_dt.unpost = PDYN_IRG;
    ws->type->desc_tbl.phigs_dt.out_dt.struct_delete = PDYN_IRG;
    ws->type->desc_tbl.phigs_dt.out_dt.ref_mod = PDYN_IRG;
}


Ws*
phg_wsb_open_ws( cph, cp_args, ret, css_srvr )
    Cp_handle		cph;
    Phg_args		*cp_args;
    Phg_ret		*ret;
    Cpx_css_srvr	*css_srvr;
{
    void		wsb_destroy_ws();
    Phg_args_open_ws	*args = &cp_args->data.open_ws;
    char		*avlist[4];
    Ws			*ws;
    XWindowAttributes	wattr;
    Phg_pex_ext_info	pex_info;

    ret->err = -1;
    if ( !(ws = phg_wsx_create( cph, args, css_srvr )) )
	return ws;

    switch ( args->type->base_type ) {
	case WST_BASE_TYPE_X_DRAWABLE:
	    ws->display = args->conn_info.display;
	    ws->drawable_id = args->conn_info.drawable_id;
	    phg_cpx_instance_connection( cph, ws->display, 0 );
	    break;

	case WST_BASE_TYPE_X_TOOL:
	    /* Get a display connection for it. */
	    if ( (ws->display = phg_cpx_connection_exists( cph, CPX_BY_NAME,
		    args->conn_info.display_name )) ) {
		/* We know PEX is supported on this display because it's in
		 * the CPs connection list.
		 */
		phg_cpx_instance_connection( cph, ws->display, 0 );
	    } else if ( ws->display = phg_utx_open_pex_display(
		    args->conn_info.display_name, &pex_info, &ret->err ) ) {
		phg_cpx_instance_connection( cph, ws->display, 1 );
	    } else {
		ERR_BUF( ws->erh, ret->err );
		goto abort;
	    }

	    /* Create the window. */
	    if ( !phg_wsx_setup_tool( ws, &args->conn_info, args->type ) )
		goto abort;
	    break;
    }

    (void)XGetWindowAttributes( ws->display, ws->drawable_id, &wattr );
    WS_SET_WS_RECT( ws, &wattr )

    /* Build an accurate WDT after the window is open. */
    avlist[0] = PHIGS_X_DISPLAY_WINDOW;
	avlist[1] = (char *)ws->display;
	avlist[2] = (char *)ws->drawable_id;
    avlist[3] = (char *)0;
    if ( ws->type = phg_wst_create( cph->erh, args->type, avlist ) ) {
	ws->type->wsid = ws->id;
	ws->type->bound_status = WST_BOUND;
	wsb_update_wdt( ws );
    } else
	goto abort;

    ws->current_colour_model =
	ws->type->desc_tbl.phigs_dt.out_dt.default_colour_model;
    ws->category = ws->type->desc_tbl.phigs_dt.ws_category;
    ws->out_ws.model.b.cssh = css_srvr->model.b.cssh;
    if ( !init_output_state( ws ) )
	goto abort;
    init_update_state( ws, ws->type );

    /* Create and initialize all the PEX resources. */
    if ( !wsb_create_resources( ws ) )
	goto abort;
    if ( !wsb_init_resources( ws ) )
	goto abort;

    if ( !phg_wsx_setup_colormap( ws, &ret->err ) ) {
	ERR_BUF(ws->erh, ret->err);
	goto abort;
    }

    wsb_load_funcs( ws );

    /* Fill in the WDT fields that depend on an open workstaton. */
    ws->type->desc_tbl.phigs_dt.out_dt.num_display_priorities = 0;
    ws->type->desc_tbl.phigs_dt.dev_coords[0] = ws->ws_rect.width;
    ws->type->desc_tbl.phigs_dt.dev_coords[1] = ws->ws_rect.height;
    ws->type->desc_tbl.phigs_dt.dev_addrs_units[0] = ws->ws_rect.width;
    ws->type->desc_tbl.phigs_dt.dev_addrs_units[1] = ws->ws_rect.height;

    /* Fill in the return data. */
    ret->err = 0;
    ret->data.open_ws.wstype = ws->type;
    ret->data.open_ws.wst_buffer = ws->type->buffer;
    ret->data.open_ws.wst_buffer_size = ws->type->buffer_size;
    ret->data.open_ws.drawable_id = ws->drawable_id;
    ret->data.open_ws.overlay_id = ws->input_overlay_window;

    return ws;

abort:
    wsb_destroy_ws( ws );
    return (Ws *)NULL;
}


void
wsb_free_all_posted( owsb )
    Wsb_output_ws	*owsb;
{
    register	Ws_post_str	*cur, *end;

    cur = owsb->posted.lowest.higher;
    end = &owsb->posted.highest;
    while ( cur != end ) {
	cur = cur->higher;
	free( (char *)cur->lower );
    }
    owsb->posted.lowest.higher = end;
    end->lower = &owsb->posted.lowest;
}


void
wsb_destroy_ws( ws )
    Ws		*ws;
{
    if ( ws ) {
	if ( ws->out_ws.model.b.views )
	    free( (char *)ws->out_ws.model.b.views );
	if ( ws->out_ws.model.b.pending_views )
	    free( (char *)ws->out_ws.model.b.pending_views );
	if ( ws->out_ws.model.b.view_priorities )
	    free( (char *)ws->out_ws.model.b.view_priorities );
	if ( ws->display ) {
	    if ( ws->drawable_id )
		phg_wsx_release_window( ws );
	    if ( ws->out_ws.model.b.pipeline )
		PEXFreePipelineContext( ws->display,
		    ws->out_ws.model.b.pipeline );
	    if ( ws->rid )
		PEXFreeRenderer( ws->display, ws->rid );
	    phg_wsx_destroy_LUTs( ws );
	    XFlush( ws->display );
	    phg_cpx_release_connection( ws->cph, ws->display );
	}
	phg_wsx_destroy( ws );
    }
}


void
phg_wsb_close_ws( ws )
    Ws		*ws;
{
    if ( ws ) {
	wsb_free_all_posted( &ws->out_ws.model.b );
	wsb_destroy_ws( ws );
    }
}


void
phg_wsb_redraw_all( ws, clear_control )
    Ws		*ws;
    Pctrl_flag	clear_control;
{
    (*ws->make_requested_current)( ws );
    (*ws->repaint_all)( ws, clear_control, 0, (XRectangle *)NULL );
    ws->out_ws.model.b.vis_rep = PVISUAL_ST_CORRECT;
}



/* Make all "requested" and pending data current. */
void
phg_wsb_make_requested_current( ws )
    Ws		*ws;
{
    Ws_view_entry	*view;
    Ws_pending_view	*req_view;
    pexViewEntry	pex_view;
    pexViewport		vp;
    pexNpcSubvolume	win;

    register Wsb_output_ws	*owsb = &ws->out_ws.model.b;

    /* WS transform */
    if ( owsb->ws_window_pending == PUPD_PEND
	    || owsb->ws_viewport_pending == PUPD_PEND ) {
	if ( owsb->ws_window_pending == PUPD_PEND ) {
	    owsb->ws_window = owsb->req_ws_window;
	    owsb->ws_window_pending = PUPD_NOT_PEND;
	    win.minval.x = owsb->ws_window.x_min;
	    win.minval.y = owsb->ws_window.y_min;
	    win.minval.z = owsb->ws_window.z_min;
	    win.maxval.x = owsb->ws_window.x_max;
	    win.maxval.y = owsb->ws_window.y_max;
	    win.maxval.z = owsb->ws_window.z_max;
	    (void)PEXChangeRenderer( ws->display, ws->rid,
		(pexBitmask)PEXRDNpcSubvolume,
		(CARD32)sizeof(pexNpcSubvolume), (char *)&win );
	}

	if ( owsb->ws_viewport_pending == PUPD_PEND ) {
	    owsb->ws_viewport = owsb->req_ws_viewport;
	    owsb->ws_viewport_pending = PUPD_NOT_PEND;
	    vp.minval.x = owsb->ws_viewport.x_min;
	    vp.minval.y = owsb->ws_viewport.y_min;
	    vp.minval.z = owsb->ws_viewport.z_min;
	    vp.maxval.x = owsb->ws_viewport.x_max;
	    vp.maxval.y = owsb->ws_viewport.y_max;
	    vp.maxval.z = owsb->ws_viewport.z_max;
	    vp.useDrawable = 0;
	    (void)PEXChangeRenderer( ws->display, ws->rid,
		(pexBitmask)PEXRDViewport,
		(CARD32)sizeof(pexViewport), (char *)&vp );
	}

	phg_wsx_compute_ws_transform( &owsb->ws_window, &owsb->ws_viewport,
	    &owsb->ws_xform );
    }

    /* View table */
    if ( owsb->num_pending_views > 0 ) {
	req_view = owsb->pending_views;
	while ( owsb->num_pending_views > 0 ) {
	    view = &owsb->views[req_view->id];
	    /*Set it locally. */
	    view->pending = PUPD_NOT_PEND;
	    bcopy( (char *)req_view->view.ori_matrix, (char *)view->vom,
		sizeof(Pmatrix3) );
	    bcopy( (char *)req_view->view.map_matrix, (char *)view->vmm,
		sizeof(Pmatrix3) );
	    view->clip_limit = req_view->view.clip_limit;
	    view->xy_clip = req_view->view.xy_clip;
	    view->back_clip = req_view->view.back_clip;
	    view->front_clip = req_view->view.front_clip;
	    view->npc_to_wc_state = WS_INV_NOT_CURRENT;

	    /* Set it in the server.  Can't set them all as a block with one
	     * request because the pending views may not be contiguous.
	     */
	    (void)phg_utx_view_entry_to_pex( &req_view->view, &pex_view );
	    (void)PEXSetTableEntries( ws->display, ws->out_ws.lut.view,
		(pexTableIndex)req_view->id, (CARD16)1,
		(CARD32)sizeof(pex_view), (char *)&pex_view );
	    ++req_view;
	    --owsb->num_pending_views;
	}
    }

    /* Other pending data */
    if ( owsb->hlhsr_mode_pending == PUPD_PEND) {
	 CARD32     new_mode;

	 owsb->cur_hlhsr_mode = owsb->req_hlhsr_mode;
	 owsb->hlhsr_mode_pending = PUPD_NOT_PEND;

	 new_mode = (CARD32)PEX_CONV_PHIGS_HLHSR_MODE(owsb->cur_hlhsr_mode);
         (void)PEXChangeRenderer( ws->display, ws->rid,
                                 (pexBitmask)PEXRDHlhsrMode,
                                 (CARD32)sizeof(CARD32), (char *)&new_mode);

    }

    /* Make it all take effect. */
    XFlush( ws->display );
}


void
phg_wsb_repaint_all( ws, clear_control, num_rects, exposure_rects )
    Ws		*ws;
    Pctrl_flag	clear_control;
    int		num_rects;		/* may be 0 */
    XRectangle	*exposure_rects;	/* may be NULL */
{
    Wsb_output_ws	*owsb = &ws->out_ws.model.b;
    int                 tmp;
    Drawable            draw;

    register int	i;

    /* assuming this stuff does clear then don't do it when using Double Buff
       since the swap will take care of that
    */
    if ((clear_control == PFLAG_ALWAYS || owsb->surf_state == PSURF_NOT_EMPTY)
       && !ws->out_ws.model.b.has_double_buffer) {
	/* TODO: Need a way to "clear" the window that uses the zero-th
	 * entry in the WS colour table and runs it through colour mapping.
	 */
	if ( num_rects > 0 ) {
	    for ( i = 0; i < num_rects; i++ )
		XClearArea( ws->display, ws->drawable_id,
		    exposure_rects[i].x, exposure_rects[i].x,
		    exposure_rects[i].width, exposure_rects[i].height,
		    False );
	} else
	    XClearWindow( ws->display, ws->drawable_id );
    }
    owsb->surf_state = PSURF_EMPTY;

    /* set the drawable correctly */
    if (ws->out_ws.model.b.has_double_buffer)
        draw = ws->out_ws.model.b.double_drawable[ws->out_ws.model.b.back];
    else  /* single buffer */
        draw = ws->drawable_id;

    phg_wsb_traverse_all_postings( ws, draw );

    /* now swap the buffers and update the drawable indices */
    if (ws->out_ws.model.b.has_double_buffer) {
          XmbufDisplayBuffers(ws->display, 1, &draw, 0, 0);
          tmp = ws->out_ws.model.b.front;
          ws->out_ws.model.b.front  = ws->out_ws.model.b.back;
          ws->out_ws.model.b.back = tmp;
    }

    /* Redraw input prompts & echos of any active input devices. */
    if ( ws->input_repaint && WS_ANY_INP_DEV_ACTIVE(ws) )
	(ws->input_repaint)( ws, num_rects, exposure_rects );

    XFlush( ws->display );
}


void
phg_wsb_traverse_all_postings( ws, draw )
    Ws		*ws;
    Drawable            draw;
{
    register Wsb_output_ws	*owsb = &ws->out_ws.model.b;
    register Ws_post_str	*post_str, *end;

    WSB_CHECK_POSTED(&owsb->posted)
    if( WSB_SOME_POSTED(&owsb->posted) ) {
	/* Set up for complete traversal. */
	post_str = owsb->posted.lowest.higher;
	end = &(owsb->posted.highest);
	PEXBeginRendering( ws->display, ws->rid, draw);
	while ( post_str != end ) {
	    phg_wsb_traverse_net( ws, post_str->structh );
	    post_str = post_str->higher;
	}
	PEXEndRendering( ws->display, ws->rid, PEXOn );
	XFlush( ws->display );
	owsb->surf_state = PSURF_NOT_EMPTY;
    }
}


void
phg_wsb_traverse_net( ws, structp )
    Ws_handle		ws;
    Struct_handle	structp;
{
    register	El_handle	el;

    PEXBeginStructure( ws->display, ws->rid, (CARD32)structp->struct_id );
    el = structp->first_el;
    while ( 1 ) {	/* termination test is at the bottom */
	switch ( el->eltype ) {
	    case PELEM_NIL:
		break;
	    case PELEM_EXEC_STRUCT:
		phg_wsb_traverse_net( ws, (Struct_handle)el->eldata.ptr );
		break;
	    default:
		PEXRenderOutputCommands( ws->display, ws->rid, (CARD32)1,
		    (CARD32)(((pexElementInfo *)el->eldata.ptr)->length
			* sizeof(CARD32)),
		    (char *)el->eldata.ptr );
		break;
	}

	if ( el == structp->last_el )
	    break;  /* out of the while over all elements in struct */
	el = el->next;
    }
    PEXEndStructure( ws->display, ws->rid );
}


static int
wsb_visible_element_type( el )
    El_handle	el;
{
    int		status = 1;	/* almost all are */

    switch ( el->eltype ) {
	case PELEM_APPL_DATA:
	case PELEM_LABEL:
	case PELEM_PICK_ID:
	    status = 0;
	    break;
    }

    return status;
}

void
phg_wsb_add_el( ws )
    Ws	*ws;
{
    Wsb_output_ws	*owsb = &ws->out_ws.model.b;
    El_handle		cur_el = CSS_CUR_ELP(owsb->cssh);

    assure(CSS_CUR_STRUCTP(owsb->cssh)); /* A structure must be open */
    WSB_CHECK_FOR_INTERACTION_UNDERWAY(ws, &owsb->now_action)
    switch ( owsb->now_action ) {
	case_PHG_UPDATE_ACCURATE_or_IF_Ix:
	default:
	    if ( wsb_visible_element_type( cur_el ) )
		(*ws->redraw_all)( ws, PFLAG_COND );
	    break;

	case PHG_UPDATE_UWOR:
	case PHG_UPDATE_NOTHING:
	case PHG_UPDATE_UQUM:
	    owsb->vis_rep = PVISUAL_ST_DEFER;
	    break;
    }
}


int
phg_wsb_asti_update( ws, clear_control )
    Ws		*ws;
    Pctrl_flag	clear_control;
{
    /* Returns non-zero if redraw occurred. */

    register	Wsb_output_ws	*owsb = &ws->out_ws.model.b;

    switch ( (*owsb->update_action_table)
	    [(int)PHG_TIME_ATI]
	    [(int)ws->out_ws.mod_mode]
	    [(int)ws->out_ws.def_mode] ) {
	case PHG_UPDATE_IF_INCORRECT:
	case PHG_UPDATE_IF_IL:
	case PHG_UPDATE_IF_IG:
	    if ( owsb->vis_rep == PVISUAL_ST_CORRECT )
		break;
	    /* else fall through to PHG_UPDATE_ACCURATE case */
	case PHG_UPDATE_ACCURATE:
	    (*ws->redraw_all)( ws, clear_control );
	    return 1;

	case PHG_UPDATE_UQUM:
	case PHG_UPDATE_UWOR:
	case PHG_UPDATE_NOTHING:
	    break;
    }

    return 0;
}

void
phg_wsb_close_struct( ws, structh )
    Ws			*ws;
    Struct_handle	structh;
{
    Wsb_output_ws	*owsb = &ws->out_ws.model.b;

    WSB_CHECK_FOR_INTERACTION_UNDERWAY(ws, &owsb->now_action)
    /* First, do processing that is independent of screen output */
    switch ( owsb->now_action ) {
	case_PHG_UPDATE_ACCURATE_or_IF_Ix:
	case PHG_UPDATE_UWOR:
	case PHG_UPDATE_NOTHING:
	case PHG_UPDATE_UQUM:
	default:
	    break;
    }

    /* Updates are implementation dependent in ASTI mode.  This is one
     * of the cases where we do an ASTI update;  we're hopefully doing the
     * application a favor.
     */
    (void)phg_wsb_asti_update( ws, PFLAG_COND );
}


static void
wsb_update_a_posting( ws, posting )
    Ws			*ws;
    Ws_post_str 	*posting;
{
    register	Wsb_output_ws	*owsb = &ws->out_ws.model.b;

    WSB_CHECK_FOR_INTERACTION_UNDERWAY(ws, &owsb->now_action)
    switch ( owsb->now_action ) {
	case_PHG_UPDATE_ACCURATE_or_IF_Ix:
	    (*ws->redraw_all)( ws, PFLAG_COND );
	    break;

	case PHG_UPDATE_UWOR:
	case PHG_UPDATE_NOTHING:
	case PHG_UPDATE_UQUM:
	    owsb->vis_rep = PVISUAL_ST_DEFER;
	    break;
    }
}


void
phg_wsb_post( ws, structh, priority, first_posting )
    Ws			*ws;
    Struct_handle	structh;
    Pfloat		priority;
    int			first_posting;	/* 0 if already posted */
{
    Ws_post_str 	*start;

    register	Wsb_output_ws	*owsb = &ws->out_ws.model.b;
    register 	Ws_post_str 	*cur, *end;
    register 	Ws_post_str 	*new;


    if ( !first_posting ) {
	/* Check to see if structure is already posted. */
	cur = owsb->posted.lowest.higher;
	end = &owsb->posted.highest;
	while ( cur != end && cur->structh != structh )
	    cur = cur->higher;
    }

    /* The structure is already_posted if (cur != end). */
    if ( !first_posting && cur != end ) {
	if( cur->higher != end && priority >= cur->higher->disp_pri ) {
	    start = end->lower;
	    assure(start == owsb->posted.highest.lower);
	    end = cur->higher;	/* insert betw. cur->higher & posted.highest */
	} else if ( cur->lower != &owsb->posted.lowest
		&& priority < cur->lower->disp_pri ) {
	    /* Will insert between start and cur->lower. */
	    start = cur->lower;
	    end = &owsb->posted.lowest;
	} else {
	    /* This is a reposting with the same *relative* prio. */
	    cur->disp_pri = priority;
	    return;
	}

	/* Struct is posted.  Remove it, but re-use its Ws_post_str entry */
	cur->lower->higher = cur->higher;
	cur->higher->lower = cur->lower;
	new = cur;

    } else {
	/* Struct is not currently posted, malloc an element. */
	if ( !(new = (Ws_post_str *)malloc(sizeof(Ws_post_str))) ) {
	    ERR_BUF( ws->erh, ERR900);
	    return;
	}
	start = owsb->posted.highest.lower;
	end = &owsb->posted.lowest;
    }

    /* Now figure out where to insert it, working backwards from start
     * to end
     */
    cur = start;
    while ( cur != end && cur->disp_pri > priority )
	cur = cur->lower;	/* if priorities equal, new after cur */
    /* insert new element w/prio >= than cur's, so cur->higher will be new */
    new->lower = cur;
    new->higher = cur->higher;
    cur->higher = new;
    new->higher->lower = new;
    new->structh = structh;
    new->disp_pri = priority;

    if ( structh->num_el != 0 )
	wsb_update_a_posting( ws, new );
}


/* This function only called from the css for change struct ids/refs -
 * it is used to change the structure pointers referencing a given
 * structure, because the way the change struct ids/refs functions work
 * is by changing struct_id fields rather than copying whole structures,
 * so any lists using structure pointers to reference specific structures
 * have to be changed to use the correct pointers.
 */

void
phg_wsb_change_posting( ws, unpost, post )
    Ws			*ws;
    Struct_handle	unpost, post;
{
    register	Wsb_output_ws	*owsb = &ws->out_ws.model.b;
    register 	Ws_post_str 	*cur, *end;

    cur = owsb->posted.lowest.higher;
    end = &owsb->posted.highest;
    while ( cur != end && cur->structh != unpost )
	cur = cur->higher;

    if ( cur != end ) {
	if ( post ) {
	    /* if the structure to be "posted" is already posted, remove it */
	    phg_wsb_change_posting( ws, post, (Struct_handle)NULL );
	    /* Change posted structure from "unpost" to "post", same priority*/
	    cur->structh = post;
	} else {
	    /* Post is NULL - just remove Ws_post_str entry for unpost. */
	    cur->lower->higher = cur->higher;
	    cur->higher->lower = cur->lower;
	    free( (char *)cur );
	}
    }
}


/* Search the list of posted structures for this one.
 * If found, return pointer to next-higher-priority posted structure element.
 * (Remember that that could be the dummy element owsb->posted.highest)
 * If not, return NULL.
 */
static Ws_post_str*
wsb_unpost_struct_if_found( owsb, structh )
    Wsb_output_ws	*owsb;
    Struct_handle	 structh;
{
    register 	Ws_post_str	*cur, *end;

    cur = owsb->posted.lowest.higher;
    end = &owsb->posted.highest;
    while ( cur != end && cur->structh != structh )
	cur = cur->higher;
    if ( cur != end ) {
	/* Found it -- now delete it */
	cur->lower->higher = cur->higher;
	cur->higher->lower = cur->lower;
	end = cur->higher;	/* Save this around the free */
	free( (char *)cur );
	return end;
    } else
	return (Ws_post_str*)NULL;
}


void
phg_wsb_unpost( ws, structh )
    Ws			*ws;
    Struct_handle	structh;
{
    register	Wsb_output_ws	*owsb = &ws->out_ws.model.b;

    if ( !wsb_unpost_struct_if_found( owsb, structh ) )
	/* Tried to unpost structure that wasn't there; but that's okay. */
	return;

    if ( structh->num_el != 0 ) {
	WSB_CHECK_FOR_INTERACTION_UNDERWAY(ws, &owsb->now_action)
	switch ( owsb->now_action ) {
	    case_PHG_UPDATE_ACCURATE_or_IF_Ix:
		(*ws->redraw_all)( ws, PFLAG_COND );
		break;

	    case PHG_UPDATE_UWOR:
	    case PHG_UPDATE_NOTHING:
	    case PHG_UPDATE_UQUM:
		owsb->vis_rep = PVISUAL_ST_DEFER;
		break;
	}
    }
}


void
phg_wsb_unpost_all( ws )
    Ws    *ws;
{
    Wsb_output_ws	*owsb = &ws->out_ws.model.b;

    wsb_free_all_posted( owsb );
    WSB_CHECK_FOR_INTERACTION_UNDERWAY(ws, &owsb->now_action)
    switch ( owsb->now_action ) {
	case_PHG_UPDATE_ACCURATE_or_IF_Ix:
	    (*ws->redraw_all)( ws, PFLAG_COND );
	    break;

	case PHG_UPDATE_UWOR:
	case PHG_UPDATE_NOTHING:
	case PHG_UPDATE_UQUM:
	    owsb->vis_rep = PVISUAL_ST_DEFER;
	    break;
    }
}


void
phg_wsb_delete_all_structs( ws )
    Ws		*ws;
{
    Wsb_output_ws	*owsb = &ws->out_ws.model.b;

    WSB_CHECK_FOR_INTERACTION_UNDERWAY(ws, &owsb->now_action)
    phg_wsb_unpost_all( ws );
}


int
phg_wsb_delete_struct( ws, structh, flag )
    Ws			*ws;
    Struct_handle	structh;
    Ws_delete_flag	flag;
{
    Wsb_output_ws	*owsb = &ws->out_ws.model.b;
    int			call_again = 0;

    WSB_CHECK_FOR_INTERACTION_UNDERWAY(ws, &owsb->now_action)
    switch ( owsb->now_action ) {
	case_PHG_UPDATE_ACCURATE_or_IF_Ix:
	    if ( flag == WS_PRE_CSS_DELETE ) {
		(void)wsb_unpost_struct_if_found( owsb, structh );
		call_again = 1;
	    } else
		(*ws->redraw_all)( ws, PFLAG_COND );
	    break;

	case PHG_UPDATE_UWOR:
	case PHG_UPDATE_NOTHING:
	case PHG_UPDATE_UQUM:
	    (void)wsb_unpost_struct_if_found( owsb, structh );
	    owsb->vis_rep = PVISUAL_ST_DEFER;
	    break;
    }

    return call_again;
}


int
phg_wsb_delete_struct_net( ws, structh, reff, flag )
    Ws			*ws;
    Struct_handle	 structh;
    Pref_flag		 reff;
    Ws_delete_flag	 flag;
{
    Wsb_output_ws	*owsb = &ws->out_ws.model.b;
    int			call_again = 0;

    WSB_CHECK_FOR_INTERACTION_UNDERWAY(ws, &owsb->now_action)
    switch ( owsb->now_action ) {
	case_PHG_UPDATE_ACCURATE_or_IF_Ix:
	    if ( flag == WS_PRE_CSS_DELETE ) {
		(void)wsb_unpost_struct_if_found( owsb, structh );
		call_again = 1;
	    } else
		(*ws->redraw_all)( ws, PFLAG_COND );
	    break;

	case PHG_UPDATE_UWOR:
	case PHG_UPDATE_NOTHING:
	case PHG_UPDATE_UQUM:
	default:
	    (void)wsb_unpost_struct_if_found( owsb, structh );
	    owsb->vis_rep = PVISUAL_ST_DEFER;
	    break;
    }
    return call_again;
}


void
phg_wsb_copy_struct( ws, first_el )
    Ws		*ws;
    El_handle	first_el;
{
    (*ws->conditional_redraw)( ws );
}


/* Delete elements elh1 through elh2, inclusive, in structure structh */
int
phg_wsb_delete_el( ws, structh, elh1, elh2, flag )
    Ws			*ws;
    Struct_handle	structh;
    El_handle		elh1, elh2;
    Ws_delete_flag	flag;
{
    Wsb_output_ws	*owsb = &ws->out_ws.model.b;
    int			call_again = 0;

    WSB_CHECK_FOR_INTERACTION_UNDERWAY(ws, &owsb->now_action)
    switch ( owsb->now_action ) {
	case_PHG_UPDATE_ACCURATE_or_IF_Ix:
	default:
	    if ( flag == WS_PRE_CSS_DELETE ) {
		if ( elh1 == elh2 && !wsb_visible_element_type( elh1 ) )
		    call_again = 0; /* avoid second call. */
		else
		    call_again = 1;
	    } else		/* POST_CSS_DELETE */
		(*ws->redraw_all)(ws, PFLAG_COND);
	    break;

	case PHG_UPDATE_UWOR:
	case PHG_UPDATE_NOTHING:
	case PHG_UPDATE_UQUM:
	    owsb->vis_rep = PVISUAL_ST_DEFER;
	    break;
    }

    return call_again;
}


/* Called by CP after difficult operations like change struct refs/ids.
 * Redraws workstation, if that is permitted, else DEFERs.
 */
void
phg_wsb_conditional_redraw( ws )
Ws		*ws;
{
    Wsb_output_ws	*owsb = &ws->out_ws.model.b;

    WSB_CHECK_FOR_INTERACTION_UNDERWAY(ws, &owsb->now_action)
    switch ( owsb->now_action ) {
	case_PHG_UPDATE_ACCURATE_or_IF_Ix:
	    (*ws->redraw_all)( ws, PFLAG_ALWAYS );
	    break;

	case PHG_UPDATE_UQUM:
	case PHG_UPDATE_UWOR:
	case PHG_UPDATE_NOTHING:
	    owsb->vis_rep = PVISUAL_ST_DEFER;
	    break;
    }
}


/* Resolves PHG_UPDATE_IF_Ix to the ASTI now_action, if no input device active.
 *
 * Check this function before every (out_ws->now_action) use.
 * Then use case_PHG_UPDATE_ACCURATE_or_IF_Ix in switch (out_ws->now_action),
 * so that PHG_UPDATE_IF_Ix acts as PHG_UPDATE_ACCURATE (i.e., ASAP)
 * while appicable input devices are (still) active.
 */
void
phg_wsb_resolve_now_action( ws, now_action_ptr )
    Ws			*ws;
    Ws_update_action	*now_action_ptr;
{
    Wsb_output_ws	*owsb = &ws->out_ws.model.b;

    switch ( *now_action_ptr ) {
      case PHG_UPDATE_IF_IL:
	/* If none active, treat like ASTI until next bnig_update(ws,ws) */
	if ( !WS_ANY_INP_DEV_ACTIVE(ws) ) {
	    *now_action_ptr = (*owsb->update_action_table)
		[(int)PHG_TIME_NOW][(int)ws->out_ws.mod_mode][(int)PDEFER_ASTI];
	}
	break;

      case PHG_UPDATE_IF_IG:
	/* If none active, treat like ASTI until next bnig_update(ws,*) */
	if ( !phg_cp_any_inp_device_active( ws->cph ) ) {
	    *now_action_ptr = (*owsb->update_action_table)
		[(int)PHG_TIME_NOW][(int)ws->out_ws.mod_mode][(int)PDEFER_ASTI];
	}
	break;
    }
}


void
phg_wsb_update( ws, flag )
    Ws		*ws;
    Pregen_flag	flag;
{
    Wsb_output_ws	*owsb = &ws->out_ws.model.b;

    if ( flag != PFLAG_POSTPONE && owsb->vis_rep != PVISUAL_ST_CORRECT ) 
	(*ws->redraw_all)( ws, PFLAG_COND );
    else
	(*ws->make_requested_current)( ws );
}


void
phg_wsb_set_disp_update_state( ws, def_mode, mod_mode )
    Ws		*ws;
    Pdefer_mode	def_mode;
    Pmod_mode	mod_mode;
{
    Ws_update_action	previous_now_action;

    register	Ws_output_ws	*out_ws = &ws->out_ws;
    register	Wsb_output_ws	*owsb = &ws->out_ws.model.b;

    out_ws->def_mode = def_mode;
    out_ws->mod_mode = mod_mode;

    previous_now_action = owsb->now_action;
    owsb->now_action = (*owsb->update_action_table)
	[(int)PHG_TIME_NOW][(int)out_ws->mod_mode][(int)out_ws->def_mode];
    assure(owsb->now_action != PHG_UPDATE_IF_INCORRECT);

    if ( owsb->now_action != previous_now_action ) {
	WSB_CHECK_FOR_INTERACTION_UNDERWAY(ws, &owsb->now_action)
	switch ( owsb->now_action ) {
	    case_PHG_UPDATE_ACCURATE_or_IF_Ix:
		if( owsb->vis_rep != PVISUAL_ST_CORRECT )
		    (*ws->redraw_all)( ws, PFLAG_COND );
		break;
	}
    }
}


void
phg_wsb_set_hlhsr_mode( ws, mode )
    Ws		*ws;
    Pint	mode;
{
    register	Wsb_output_ws	*owsb = &ws->out_ws.model.b;

    if( ws->type->desc_tbl.phigs_dt.num_hlhsr_modes == 1 )
	return; /* No need to update if the user asks for what he has! */

    owsb->req_hlhsr_mode = mode;
    owsb->hlhsr_mode_pending = PUPD_PEND;

    WSB_CHECK_FOR_INTERACTION_UNDERWAY(ws, &owsb->now_action)
    switch ( owsb->now_action ) {
	case_PHG_UPDATE_ACCURATE_or_IF_Ix:
	    (*ws->redraw_all)( ws, PFLAG_COND );
	    break;

	case PHG_UPDATE_UWOR:
	case PHG_UPDATE_UQUM:
	case PHG_UPDATE_NOTHING:
	default:
	    owsb->vis_rep = PVISUAL_ST_DEFER;
	    break;
    }
}


void
phg_wsb_set_ws_window( ws, two_d, limits )
    Ws		*ws;
    int		two_d;
    Plimit3	*limits;
{
    register	Wsb_output_ws	*owsb = &ws->out_ws.model.b;

    owsb->ws_window_pending = PUPD_PEND;
    if ( two_d ) {	/* leave the z values as they are */
	owsb->req_ws_window.x_min = limits->x_min;
	owsb->req_ws_window.x_max = limits->x_max;
	owsb->req_ws_window.y_min = limits->y_min;
	owsb->req_ws_window.y_max = limits->y_max;
    } else
	owsb->req_ws_window = *limits;

    WSB_CHECK_FOR_INTERACTION_UNDERWAY(ws, &owsb->now_action)
    switch ( owsb->now_action ) {
	case_PHG_UPDATE_ACCURATE_or_IF_Ix:
	    (*ws->redraw_all)( ws, PFLAG_COND );
	    break;

	case PHG_UPDATE_UQUM:
	case PHG_UPDATE_UWOR:
	case PHG_UPDATE_NOTHING:
	default:
	    owsb->vis_rep = PVISUAL_ST_DEFER;
	    break;
    }
}


void
phg_wsb_set_ws_vp( ws, two_d, limits )
    Ws		*ws;
    int		two_d;
    Plimit3	*limits;
{
    register	Wsb_output_ws	*owsb = &ws->out_ws.model.b;

    owsb->ws_viewport_pending = PUPD_PEND;
    if ( two_d ) {	/* leave the z values as they are */
	owsb->req_ws_viewport.x_min = limits->x_min;
	owsb->req_ws_viewport.x_max = limits->x_max;
	owsb->req_ws_viewport.y_min = limits->y_min;
	owsb->req_ws_viewport.y_max = limits->y_max;
    } else
	owsb->req_ws_viewport = *limits;

    WSB_CHECK_FOR_INTERACTION_UNDERWAY(ws, &owsb->now_action)
    switch ( owsb->now_action ) {
	case_PHG_UPDATE_ACCURATE_or_IF_Ix:
	    (*ws->redraw_all)( ws, PFLAG_COND );
	    break;

	case PHG_UPDATE_UQUM:
	case PHG_UPDATE_UWOR:
	case PHG_UPDATE_NOTHING:
	default:
	    owsb->vis_rep = PVISUAL_ST_DEFER;
	    break;
    }
}


void
phg_wsb_set_view_input_priority( ws, index, ref_index, priority )
    Ws			*ws;
    register Pint	index;
    Pint		ref_index;
    Prel_pri		priority;
{
    Pint			old;

    register	Ws_view_priority	*ref, *idx;
    register	Pint			*highest;
    register	Ws_view_priority	*vp;

    vp = ws->out_ws.model.b.view_priorities;
    highest = &ws->out_ws.model.b.top_view;
    idx = &vp[index];
    ref = &vp[ref_index];

    if ( priority == PPRI_LOWER ) {
	if ( ref->lower != index ) {
	    if ( index == *highest)
		*highest = idx->lower;

	    old = ref->lower;
	    if ( ref->lower != -1 )	/* if ref not lowest priority */
		vp[ref->lower].higher = index;
	    ref->lower = index;

	    if ( idx->higher != -1 )	 /* if idx not highest priority */
		vp[idx->higher].lower = idx->lower;
	    if ( idx->lower != -1 )	 /* if idx not lowest priority */
		vp[idx->lower].higher = idx->higher;
	    idx->higher = ref_index;
	    idx->lower = old;
	}
	/* Don't need to do anything if priority is already as desired. */

    } else if ( ref->higher != index ) {	/* PPRI_HIGHER */
	if ( ref_index == *highest )
	    *highest = index;
	else if ( index == *highest )
	    *highest = idx->lower;

	old = ref->higher;
	if ( ref->higher != -1 )
	    vp[ref->higher].lower = index;
	ref->higher = index;

	if ( idx->higher != -1 )
	    vp[idx->higher].lower = idx->lower;
	if ( idx->lower != -1 )
	    vp[idx->lower].higher = idx->higher;
	idx->lower = ref_index;
	idx->higher = old;
    }

    /* Has no effect on the screen */
}


void
phg_wsb_set_rep( ws, type, rep )
    Ws			*ws;
    Phg_args_rep_type	type;
    Phg_args_rep_data	*rep;
{
    register	Wsb_output_ws	*owsb = &ws->out_ws.model.b;
    register	int		i;

    switch ( type ) {
	case PHG_ARGS_LNREP:
	case PHG_ARGS_EXTLNREP:
	case PHG_ARGS_MKREP:
	case PHG_ARGS_EXTMKREP:
	case PHG_ARGS_TXREP:
	case PHG_ARGS_EXTTXREP:
	case PHG_ARGS_INTERREP:
	case PHG_ARGS_EXTINTERREP:
	case PHG_ARGS_EDGEREP:
	case PHG_ARGS_EXTEDGEREP:
	case PHG_ARGS_PTREP:
	case PHG_ARGS_EXTPTREP:
	case PHG_ARGS_DCUEREP:
	case PHG_ARGS_LIGHTSRCREP:
	case PHG_ARGS_COLRMAPREP:
	    phg_wsx_set_LUT_entry( ws, type, rep, (Pgcolr*)NULL );
	    break;

	case PHG_ARGS_VIEWREP:
	    /* Add it to the list of pending views. */
	    if ( owsb->views[rep->index].pending == PUPD_NOT_PEND )
		i = owsb->num_pending_views++;
	    else {
		/* Find the existing pending entry so it can be replaced. */
		for ( i = 0; i < owsb->num_pending_views; i++ )
		    if ( owsb->pending_views[i].id == rep->index )
			break;
	    }
	    owsb->pending_views[i].id = rep->index;
	    owsb->pending_views[i].view = rep->bundl.viewrep;
	    owsb->views[rep->index].pending = PUPD_PEND;
	    break;

	case PHG_ARGS_COREP: {
	    Pgcolr	gcolr;

	    /* Store in current colour model. */
	    gcolr.type = ws->current_colour_model;
	    gcolr.val.general.x = rep->bundl.corep.rgb.red;
	    gcolr.val.general.y = rep->bundl.corep.rgb.green;
	    gcolr.val.general.z = rep->bundl.corep.rgb.blue;
	    phg_wsx_set_LUT_entry( ws, type, rep, &gcolr );
	    } break;
    }

    WSB_CHECK_FOR_INTERACTION_UNDERWAY(ws, &owsb->now_action)
    switch ( owsb->now_action ) {
	case_PHG_UPDATE_ACCURATE_or_IF_Ix:
	    (*ws->redraw_all)( ws, PFLAG_COND );
	    break;

	default:
	case PHG_UPDATE_UQUM:
	case PHG_UPDATE_UWOR:
	    owsb->vis_rep = PVISUAL_ST_DEFER;
	    break;

	case PHG_UPDATE_NOTHING:
	    /* Defer if rep has PENDING flag, or if screen could be affected. */
	    if ( WSB_SOME_POSTED(&owsb->posted) || (type == PHG_ARGS_VIEWREP) )
		 owsb->vis_rep = PVISUAL_ST_DEFER;
	    break;
    }
}

void
phg_wsb_set_filter( ws, type, devid, inc_set, exc_set )
    Ws			*ws;
    Phg_args_flt_type	type;
    Pint		devid;
    Pint_list		*inc_set;
    Pint_list		*exc_set;
{
    Wsb_output_ws	*owsb = &ws->out_ws.model.b;

    phg_wsx_set_name_set( ws, type, devid, inc_set, exc_set );

    if ( (type == PHG_ARGS_FLT_HIGH || type == PHG_ARGS_FLT_INVIS)
	    && WSB_SOME_POSTED(&owsb->posted) ) {
	WSB_CHECK_FOR_INTERACTION_UNDERWAY(ws, &owsb->now_action)
	switch ( owsb->now_action ) {
	    case_PHG_UPDATE_ACCURATE_or_IF_Ix:
		(*ws->redraw_all)( ws, PFLAG_COND );
		break;

	    case PHG_UPDATE_UQUM:
	    case PHG_UPDATE_UWOR:
	    case PHG_UPDATE_NOTHING:
	    default:
		owsb->vis_rep = PVISUAL_ST_DEFER;
		break;
	}
    }
}


void
phg_wsb_inq_view_indices( ws, ret )
    Ws		*ws;
    Phg_ret	*ret;
{
    Wsb_output_ws	*owsb = &ws->out_ws.model.b;

    register	Pint			*list, view;
    register	int			i;
    register	Ws_view_priority	*prio = owsb->view_priorities;

    if ( !PHG_SCRATCH_SPACE( &ws->scratch, owsb->num_views * sizeof(Pint))) {
	ret->err = ERR900;

    } else {
	ret->err = 0;
	ret->data.int_list.num_ints = owsb->num_views;
	ret->data.int_list.ints = list = (Pint *)ws->scratch.buf;
	view = owsb->top_view;
	i = 0;
	while ( view != -1 ) {
	    list[i++] = view;
	    view = prio[view].lower;
	}
    }
}


void
phg_wsb_inq_posted( ws, ret )
    Ws		*ws;
    Phg_ret	*ret;
{
    Wsb_output_ws	*owsb = &ws->out_ws.model.b;

    register	Ws_post_str	*cur, *end;
    register	int		cnt;
    register	Pposted_struct	*list;

    cur = owsb->posted.lowest.higher;
    end = &owsb->posted.highest;

    /* Count them */
    cnt = 0;
    while ( cur != end ) {
	++cnt;
	cur = cur->higher;
    }

    ret->err = 0;
    ret->data.postlist.num_postings = cnt;
    if ( cnt > 0 ) {
	if ( PHG_SCRATCH_SPACE(&ws->scratch, cnt * sizeof(Pposted_struct)) ) {
	    ret->data.postlist.postings = list =
		(Pposted_struct *)ws->scratch.buf;
	    cur = owsb->posted.lowest.higher;
	    while ( cur != end ) {
		list->id = cur->structh->struct_id;
		list++->disp_pri = cur->disp_pri;
		cur = cur->higher;
	    }

	} else {
	    ret->err = ERR900;
	    ret->data.postlist.num_postings = 0;
	}
    }
}


void
phg_wsb_inq_view_rep( ws, index, ret )
    Ws		*ws;
    Pint	index;
    Phg_ret	*ret;
{
    register int		i;
    register Pview_rep3		*cr;
    register Ws_view_entry	*cv;
    register Wsb_output_ws	*owsb = &ws->out_ws.model.b;
    register Phg_ret_view_rep	*vr = &ret->data.view_rep;

    ret->err = 0;
    vr->update_state = owsb->views[index].pending;
    /* Load the "current" view. */
    if ( !PHG_SCRATCH_SPACE( &ws->scratch, sizeof(Pview_rep3) ) ) {
	ret->err = ERR900;
	return;
    } else {
	vr->cur_rep = cr = (Pview_rep3 *)ws->scratch.buf;
	cv = &owsb->views[index];
	cr->clip_limit = cv->clip_limit;
	cr->xy_clip = cv->xy_clip;
	cr->back_clip = cv->back_clip;
	cr->front_clip = cv->front_clip;
	bcopy( (char *)cv->vom, (char *)cr->ori_matrix, sizeof(Pmatrix3) );
	bcopy( (char *)cv->vmm, (char *)cr->map_matrix, sizeof(Pmatrix3) );
    }

    /* Load the "requested" view. */
    if ( vr->update_state == PUPD_NOT_PEND )	/* save some time */
	vr->req_rep = vr->cur_rep;
    else {
	/* Find the requested entry in the pending view list. */
	for ( i = 0; i < owsb->num_pending_views; i++ ) {
	    if ( owsb->pending_views[i].id == index ) {
		cr = &owsb->pending_views[i].view;
		break;	/* we've found it, no need to keep searching */
	    }
	}
	vr->req_rep = cr;
    }
}


void
phg_wsb_inq_ws_xform( ws, ret )
    Ws		*ws;
    Phg_ret	*ret;
{
    register Phg_ret_ws_tran3	*wsxf = &ret->data.ws_xform;
    register Wsb_output_ws	*owsb = &ws->out_ws.model.b;

    ret->err = 0;
    wsxf->state =
	   owsb->ws_window_pending == PUPD_PEND
	|| owsb->ws_viewport_pending == PUPD_PEND
	    ? PUPD_PEND : PUPD_NOT_PEND;
    wsxf->req_window = owsb->req_ws_window;
    wsxf->req_viewport = owsb->req_ws_viewport;
    wsxf->cur_window = owsb->ws_window;
    wsxf->cur_viewport = owsb->ws_viewport;
}


void
phg_wsb_inq_disp_update_state( ws, ret)
    Ws			*ws;
    Phg_ret		*ret;
{
    Wsb_output_ws	*owsb = &ws->out_ws.model.b;

    ret->err = 0;
    ret->data.update_state.def_mode = ws->out_ws.def_mode;
    ret->data.update_state.mod_mode = ws->out_ws.mod_mode;
    ret->data.update_state.display_surf = owsb->surf_state;
    ret->data.update_state.state = owsb->vis_rep;
}


void
phg_wsb_inq_hlhsr_mode( ws, ret )
    Ws			*ws;
    Phg_ret		*ret;
{
    Wsb_output_ws	*owsb = &ws->out_ws.model.b;

    ret->err = 0;
    ret->data.hlhsr_mode.state =  owsb->hlhsr_mode_pending;
    ret->data.hlhsr_mode.cur_mode = owsb->cur_hlhsr_mode;
    ret->data.hlhsr_mode.req_mode = owsb->req_hlhsr_mode;
}


void
phg_wsb_inq_rep( ws, index, how, rep_type, ret )
    Ws			*ws;
    Pint		index;
    Pinq_type		how;		/* set or realized */
    Phg_args_rep_type	rep_type;
    Phg_ret		*ret;
{
    ret->err = 0;
    switch ( rep_type ) {
	case PHG_ARGS_LNREP:
	case PHG_ARGS_EXTLNREP:
	case PHG_ARGS_MKREP:
	case PHG_ARGS_EXTMKREP:
	case PHG_ARGS_TXREP:
	case PHG_ARGS_EXTTXREP:
	case PHG_ARGS_INTERREP:
	case PHG_ARGS_EXTINTERREP:
	case PHG_ARGS_EDGEREP:
	case PHG_ARGS_EXTEDGEREP:
	case PHG_ARGS_PTREP:
	case PHG_ARGS_EXTPTREP:
	case PHG_ARGS_DCUEREP:
	case PHG_ARGS_LIGHTSRCREP:
	case PHG_ARGS_COLRMAPREP:
	/* View rep is done elsewhere. */
	    phg_wsx_inq_LUT_entry( ws, index, how, rep_type, ret,
		(Pgcolr *)NULL, (Pview_rep3 *)NULL );
	    break;

	case PHG_ARGS_COREP: {
	    Pgcolr		src_gcolr, gcolr;
	    Pcolr_rep	*cb = &ret->data.rep.corep;

	    /* Need to convert to current colour model. */
	    phg_wsx_inq_LUT_entry( ws, index, how, rep_type, ret,
		&src_gcolr, (Pview_rep3 *)NULL );
	    gcolr.type = ws->current_colour_model;
	    (void)phg_utx_convert_colour( &src_gcolr, &gcolr,
		&ws->type->desc_tbl.phigs_dt.out_dt.chroma_info );
	    cb->rgb.red = gcolr.val.general.x;
	    cb->rgb.green = gcolr.val.general.y;
	    cb->rgb.blue = gcolr.val.general.z;
	    } break;
    }
}


static void
update_inv_view_xform( view )
    Ws_view_entry	*view;
{
     /* Calculate the inverse xform, if necessary. */
    if ( view->npc_to_wc_state == WS_INV_NOT_CURRENT ) {
	phg_mat_mul( view->npc_to_wc, view->vmm, view->vom );
	phg_mat_inv( view->npc_to_wc );
	view->npc_to_wc_state = WS_INV_CURRENT;
    }
}


int
phg_wsb_map_initial_points( ws, view_index, num_pts, wc_pts, dwbl_pts )
    Ws		*ws;
    Pint	view_index;
    Pint	*num_pts;
    Ppoint3	*wc_pts;
    XPoint	*dwbl_pts;
{
    Ppoint3	scratch[20];	/* enough for most cases */
    Pmatrix3	wc_to_npc;
    Ppoint3	dc_pt;

    register	Ppoint3		*npc_pts = (Ppoint3 *)NULL;
    register	int		i;
    register	Ws_view_entry	*view;
    register	Ws_xform	*wsxf = &ws->out_ws.model.b.ws_xform;

    /* Transform the initial points to NPC and check that they all fit
     * in the clip limits of the specified view.  Then transform and map
     * them to drawable coordinates.
     */
    if ( *num_pts <= 0 )
	return 0;
    if ( *num_pts <= sizeof(scratch)/sizeof(scratch[0]) )
	npc_pts = scratch;
    else if ( *num_pts > sizeof(scratch)/sizeof(scratch[0]) ) {
	if ( !(npc_pts = (Ppoint3 *)malloc( (unsigned)(*num_pts *
		sizeof(Ppoint3)) )) ) {
	    *num_pts = 0;
	    ERR_BUF( ws->erh, ERR900 );
	    return 0;
	}
    }

    view = &ws->out_ws.model.b.views[view_index];
    phg_mat_mul( wc_to_npc, view->vmm, view->vom );
    if ( !phg_tranpts3( wc_to_npc, *num_pts, wc_pts, npc_pts ) ) {
	*num_pts = 0;
	return 0;
    }

    for ( i = 0; i < *num_pts; i++ ) {
	if ( !WS_PT_IN_LIMIT( &view->clip_limit, &npc_pts[i] ) ) {
	    *num_pts = 0;
	    break;
	} else {
	    WS_NPC_TO_DC( wsxf, &npc_pts[i], &dc_pt )
	    WS_DC_TO_DRWBL2( ws, &dc_pt, &dwbl_pts[i] );
	}
    }

    if ( npc_pts && npc_pts != scratch )
	free( (char *)npc_pts );

    return ( *num_pts > 0 ? 1 : 0 );
}


int
phg_wsb_resolve_locator( ws, dc_pt, determine_z, view_index, wc_pt )
    Ws			*ws;
    pexDeviceCoord      *dc_pt;
    int			determine_z;	/* ignored */
    Pint		*view_index;
    Ppoint3		*wc_pt;
{
    Ppoint3		npc_pt;
    Wsb_output_ws	*owsb = &ws->out_ws.model.b;
    Ws_view_priority	*priorities = owsb->view_priorities;
    Ws_xform		*wsxf = &owsb->ws_xform;
    Plimit3		*ws_win = &owsb->ws_window;
    int			status = 0;

    register Ws_view_entry	*view;

    /* Apply the inverse WS transform and see if it's in the ws window.
     * Can't just check against the viewport boundaries because the
     * window may be smaller if the aspect ratios are different.
     */
    WS_DC_TO_NPC2(wsxf, dc_pt, &npc_pt)
    if ( npc_pt.x >= ws_win->x_min && npc_pt.x <= ws_win->x_max
	&& npc_pt.y >= ws_win->y_min && npc_pt.y <= ws_win->y_max ) {

	/* Find the highest priority view that contains the point. */
	for ( *view_index = owsb->top_view; *view_index != -1;
		*view_index = priorities[*view_index].lower ) {
	    view = &owsb->views[*view_index];
	    if ( WS_PT_IN_LIMIT2(&view->clip_limit, &npc_pt) ) {
		/* Assign the clip window minimum to Z. */
		npc_pt.z = view->clip_limit.z_min;

		/* Calculate the inverse xform if necessary. */
		if ( view->npc_to_wc_state == WS_INV_NOT_CURRENT )
		    update_inv_view_xform( view );
  
		/* Map point to WC if xform invertible. */
		if ( view->npc_to_wc_state == WS_INV_CURRENT ) {
		    if ( phg_tranpt3( &npc_pt, view->npc_to_wc, wc_pt ) ) {
			status = 1;
			break;	/* out of the for on view index */
		    }
		}
	    }
	}
    }
    return status;
}


static int
wsb_resolve_stroke( ws, two_d, dc_ll, dc_ur, view_index )
    Ws			*ws;
    int			two_d;	/* input points are 2D */
    pexDeviceCoord	*dc_ll, *dc_ur;
    Pint		*view_index;	/* resolved view index */

    /* Returns 1 if stroke resolvable, else 0 */
{
    Ppoint3		npc_ll, npc_ur;
    Wsb_output_ws	*owsb = &ws->out_ws.model.b;
    Ws_view_priority	*priorities = owsb->view_priorities;
    Ws_xform		*wsxf = &owsb->ws_xform;
    Plimit3		*ws_win = &owsb->ws_window;
    int			status = 0, in_limit;

    register Ws_view_entry	*view;

    /* Apply the inverse WS transform and see if the bounding box is in
     * the ws window.  Can't just check against the viewport boundaries
     * because the window may be smaller if the aspect ratios are different.
     */
    in_limit = 0;
    if ( two_d ) {
	WS_DC_TO_NPC2(wsxf, dc_ll, &npc_ll)
	WS_DC_TO_NPC2(wsxf, dc_ur, &npc_ur)
	in_limit = WS_PT_IN_LIMIT2(ws_win, &npc_ll)
	    && WS_PT_IN_LIMIT2(ws_win, &npc_ur);
    } else {
	WS_DC_TO_NPC(wsxf, dc_ll, &npc_ll)
	WS_DC_TO_NPC(wsxf, dc_ur, &npc_ur)
	in_limit = WS_PT_IN_LIMIT(ws_win, &npc_ll)
	    && WS_PT_IN_LIMIT(ws_win, &npc_ur);
    }

    if ( in_limit ) {
	/* Find the highest priority view that contains the bounding box. */
	for ( *view_index = owsb->top_view; *view_index != -1;
		*view_index = priorities[*view_index].lower ) {
	    view = &owsb->views[*view_index];
	    in_limit = 0;
	    if ( two_d )
		in_limit = WS_PT_IN_LIMIT2(&view->clip_limit, &npc_ll)
		    && WS_PT_IN_LIMIT2(&view->clip_limit, &npc_ur);
	    else
		in_limit = WS_PT_IN_LIMIT(&view->clip_limit, &npc_ll)
		    && WS_PT_IN_LIMIT(&view->clip_limit, &npc_ur);

	    if ( in_limit ) {
		/* Found the view.  Calculate its inverse transform if
		 * necessary.
		 */
		if ( view->npc_to_wc_state == WS_INV_NOT_CURRENT )
		    update_inv_view_xform( view );

		if ( view->npc_to_wc_state == WS_INV_CURRENT ) {
		    status = 1;
		    break;  /* break out of the view loop when view found */
		}
	    }
	}
    }
    return status;
}


static void
wsb_transform_stroke( ws, view_index, two_d, num_pts, dc_pts, wc_pts )
		Ws		*ws;
		Pint		view_index;
		int		two_d;
    register	int		num_pts;
    register	pexDeviceCoord	*dc_pts;
		Ppoint_list3	*wc_pts;
{
    register int		i;
    register Ppoint3		*npc_pts;
    register Ws_xform		*wsxf = &ws->out_ws.model.b.ws_xform;
    register Ws_view_entry	*view = &ws->out_ws.model.b.views[view_index];

    /* Shouldn't call this function with num_pts == 0. */
    if ( !(npc_pts = (Ppoint3 *)
	    malloc( (unsigned)(num_pts * sizeof(Ppoint3)) )) ) {
	wc_pts->num_points = 0;
	ERR_BUF( ws->erh, ERR900 );
	return;
    }

    /* Transform the points to npc and add the z value. */
    for ( i = 0; i < num_pts; i++ ) {
	if ( two_d ) {
	    WS_DC_TO_NPC2(wsxf, &dc_pts[i], &npc_pts[i])
	    /* Assign the back clip limit. */
	    npc_pts[i].z = view->clip_limit.z_min;
	} else {
	    WS_DC_TO_NPC(wsxf, &dc_pts[i], &npc_pts[i])
	}
    }

    /* Transform the points to wc. */
    if ( !phg_tranpts3( view->npc_to_wc, num_pts, npc_pts, wc_pts->points ) )
	wc_pts->num_points = 0;

    free( (char *)npc_pts );
}


int
phg_wsb_resolve_stroke( ws, num_pts, dc_pts, determine_z, view_index, wc_pts )
		Ws		*ws;
    register	int		num_pts;
		pexDeviceCoord	*dc_pts;
		int		determine_z;	/* ignored */
		Pint		*view_index;
		Ppoint_list3	*wc_pts;
{
    pexDeviceCoord	ll, ur;
    int			status = 0, two_d = determine_z;

    register pexDeviceCoord	*dp;
    register int		i, xmin, xmax, ymin, ymax, zmin, zmax;

    /* Get the bounding box of all the points. */
    xmin = dc_pts->x; xmax = dc_pts->x;
    ymin = dc_pts->y; ymax = dc_pts->y;
    if ( !two_d )
	zmin = dc_pts->z, zmax = dc_pts->z;
    for ( i = 1, dp = &dc_pts[1]; i < num_pts; i++, dp++ ) {
	if ( dp->x < xmin )
	    xmin = dp->x;
	else if ( dp->x > xmax )
	    xmax = dp->x;

	if ( dp->y < ymin )
	    ymin = dp->y;
	else if ( dp->y > ymax )
	    ymax = dp->y;
	
	if ( !two_d ) {
	    /* The incoming points have Z values. */
	    if ( dp->z < zmin )
		zmin = dp->z;
	    else if ( dp->z > zmax )
		zmax = dp->z;
	}
    }
    ll.x = xmin; ll.y = ymax;
    ur.x = xmax; ur.y = ymin;
    if ( !two_d )
	ur.x = xmax, ur.y = ymin;

    /* Resolve and transform the points.  Don't change the current
     * measure if the points can't be resolved.
     */
    if ( wsb_resolve_stroke( ws, two_d, &ll, &ur, view_index ) ) {
	wc_pts->num_points = num_pts;
	wsb_transform_stroke( ws, *view_index, two_d, num_pts, dc_pts, wc_pts );
	status = 1;
    }

    return status;
}


int
phg_wsb_resolve_pick( ws, dev, echo, dc_pt, pick )
    Ws			*ws;
    Ws_inp_pick		*dev;
    int			echo;
    pexDeviceCoord      *dc_pt;
    Ppick		*pick;
{
    register Wsb_output_ws	*owsb = &ws->out_ws.model.b;
    register Ws_post_str	*post_str, *end;
    pexDeviceCoord2D		dc_vol[2];
    Ppoint			npc_pt[2];
    pexPickElementRef		*pickPath;
    int				pickDepth;
    int				betterPick, i;

    CARD32			pickDataBytes;

    struct {
	pexEnumTypeIndex pickType;
	CARD16		 unused;
	union {
	    pexPD_DC_HitBox	dcHitBox;
	    pexPD_NPC_HitVolume npcHitVolume;
	} pickRec;
    } pickData;
    
    WSB_CHECK_POSTED (&owsb->posted)

    if (WSB_SOME_POSTED (&owsb->posted)) {

	/*
	 * Use pick rendering to get the pick results.  Call BeginPickOne, do
	 * a complete traversal (starting with highest priority structure,
	 * then call EndPickOne to get the pick results.
	 */

	if (dev->dev_type == PEXPickDeviceDC_HitBox) {

	    pickData.pickType = PEXPickDeviceDC_HitBox;

	    pickData.pickRec.dcHitBox.position.x = dc_pt->x;
	    pickData.pickRec.dcHitBox.position.y = dc_pt->y;
	    pickData.pickRec.dcHitBox.distance = dev->ap_size;

	    pickDataBytes = 4 + sizeof (pexPD_DC_HitBox);

	} else {

	    pickData.pickType = PEXPickDeviceNPC_HitVolume;

	    dc_vol[0].x = dc_pt->x - dev->ap_size;
	    dc_vol[0].y = dc_pt->y - dev->ap_size;
	    dc_vol[1].x = dc_pt->x + dev->ap_size;
	    dc_vol[1].y = dc_pt->y + dev->ap_size;

	    WS_DC_TO_NPC2 (&owsb->ws_xform, &dc_vol[0], &npc_pt[0])
	    WS_DC_TO_NPC2 (&owsb->ws_xform, &dc_vol[1], &npc_pt[1])

	    pickData.pickRec.npcHitVolume.minval.x = npc_pt[0].x;
	    pickData.pickRec.npcHitVolume.minval.y = npc_pt[0].y;
	    pickData.pickRec.npcHitVolume.maxval.x = npc_pt[1].x;
	    pickData.pickRec.npcHitVolume.maxval.y = npc_pt[1].y;
	    pickData.pickRec.npcHitVolume.minval.z = owsb->ws_window.z_min;
	    pickData.pickRec.npcHitVolume.maxval.z = owsb->ws_window.z_max;

	    pickDataBytes = 4 + sizeof (pexPD_NPC_HitVolume);
	}

	PEXChangeRenderer (ws->display, ws->rid,
		(pexBitmask) PEXRDPickInclusion, (CARD32) sizeof (pexNameSet),
		(char *) &(dev->filter.incl));

	PEXChangeRenderer (ws->display, ws->rid,
		(pexBitmask) PEXRDPickExclusion, (CARD32) sizeof (pexNameSet),
		(char *) &(dev->filter.excl));


	PEXBeginPickOne (ws->display, ws->rid, ws->drawable_id, -1,
		PEXLast, pickDataBytes, &pickData);

	post_str = owsb->posted.highest.lower;
	end = &(owsb->posted.lowest);

	while (post_str != end) {
	    phg_wsb_traverse_net (ws, post_str->structh);
	    post_str = post_str->lower;
	}

	PEXEndPickOne (ws->display, ws->rid, &betterPick,
	    &pickPath, &pickDepth);
    }


    if (!pickDepth) {

    	pick->status = PIN_STATUS_NONE;
    	pick->pick_path.depth = 0;
    	pick->pick_path.path_list = (Ppick_path_elem *) NULL;

    } else {

	/*
	 * The protocol pick element ref data structure has its fields
	 * layed out in a different order than the PHIGS data structure.
	 * We must repack the data into PHIGS format.
	 */

	for (i = 1; i < pickDepth; i++) {
	    Ppick_path_elem *dst = (Ppick_path_elem *) &pickPath[i];
	    pexPickElementRef src;

	    src = pickPath[i];
	    dst->struct_id = src.sid;
	    dst->pick_id = src.pickid;
	    dst->elem_pos = src.offset;
	}

	/*
	 * order = bottom first?
	 */

	if (dev->order == PORDER_BOTTOM_FIRST) {
	    int			head, tail;
	    pexPickElementRef 	temp;

	    head = 1;
	    tail = pickDepth - 1;

	    for (i = 0; i < (pickDepth - 1) / 2; i++) {
		temp = pickPath[head];
		pickPath[head] = pickPath[tail];
		pickPath[tail] = temp;
		head++;
		tail--;
	    }
	}

	/*
	 * return status and pick path
	 */

	pick->status = PIN_STATUS_OK;
	pick->pick_path.depth = pickDepth - 1;
	pick->pick_path.path_list = (Ppick_path_elem *) &(pickPath[1]);
    }

    return 1;
}

void
phg_wsb_inq_filter( ws, type, ret )
    Ws			*ws;
    Phg_args_flt_type	type;
    Phg_ret		*ret;
{
    phg_wsx_inq_name_set( ws, type, (Pint)0, ret );
}


void
phg_wsb_drawable_pick( ws, args, ret )
    Ws				*ws;
    Phg_args_drawable_pick	*args;
    Phg_ret			*ret;
{
    ret->err = ERRN500;
    ERR_BUF( ws->erh, ret->err );
    ret->data.drawable_pick.status = PIN_STATUS_NO_IN;
    ret->data.drawable_pick.pick.depth = 0;
    ret->data.drawable_pick.pick.path_list = (Ppick_path_elem *)NULL;
}


void
phg_wsb_map_points( ws, args, ret )
    Ws				*ws;
    Phg_args_map_points		*args;
    Phg_ret			*ret;
{
    pexDeviceCoord	*dc_pts;

    register int	i;

    ret->err = 0;
    ret->data.map_points.view_index = 0;
    ret->data.map_points.points.num_points = 0;
    ret->data.map_points.points.points = (Ppoint3 *)NULL;

    /* Allocate space for both the DC and WC points. */
    if ( !PHG_SCRATCH_SPACE( &ws->scratch,
	    (unsigned)(args->points.num_points
		* (sizeof(Ppoint) + sizeof(Ppoint3))) ) ) {
	ERR_BUF( ws->erh, ERR900 );
	return;
    }
    dc_pts = (pexDeviceCoord *)ws->scratch.buf;
    ret->data.map_points.points.points =
	(Ppoint3 *)(dc_pts + args->points.num_points);

    /* Convert the points to DC. */
    phg_wsx_update_ws_rect( ws );
    for ( i = 0; i < args->points.num_points; i++ ) {
	/* Z coord is already in DC. */
	WS_DRWBL_TO_DC2(ws, &args->points.points[i], &dc_pts[i]);
	dc_pts[i].z = args->points.points[i].z;
    }

    /* Convert the DC points to WC. */
    if ( !phg_wsb_resolve_stroke( ws, args->points.num_points, dc_pts, 0,
	    &ret->data.map_points.view_index,
	    &ret->data.map_points.points ) ) {
	ret->data.map_points.points.num_points = 0;
    }
}

void
phg_wsb_redraw_regions( ws, args )
    Ws				*ws;
    Phg_args_redraw_regions	*args;
{
    pexDeviceRect	*pex_rects;
    pexBitmask		rmask;
    CARD32		*card32_p;

    if ( args->num_regions <= 0 )
	return;

    if ( !(pex_rects = (pexDeviceRect *)PHG_SCRATCH_SPACE( &ws->scratch,
	    sizeof(CARD32) +
	    (unsigned)(args->num_regions * sizeof(*pex_rects)) )) ) {
	ERR_BUF( ws->erh, ERR900 );
	return;
    }
    card32_p = (CARD32 *)ws->scratch.buf;
    pex_rects = (pexDeviceRect *)(card32_p + 1);

    *card32_p = args->num_regions;
    phg_wsx_convert_rects( ws, args->num_regions, args->regions, pex_rects );
    rmask = PEXRDClipList;
    (void)PEXChangeRenderer( ws->display, ws->rid, rmask,
	(CARD32)(sizeof(CARD32) + args->num_regions * sizeof(pexDeviceRect)),
	(char *)card32_p );
    (*ws->repaint_all)( ws, PFLAG_COND, args->num_regions, args->regions );

    /* Reset the renderer's clip list. */ 
    *card32_p = 0;
    (void)PEXChangeRenderer( ws->display, ws->rid, rmask,(CARD32)sizeof(CARD32),
	(char *)card32_p );
}