/*
 * CDE - Common Desktop Environment
 *
 * Copyright (c) 1993-2012, The Open Group. All rights reserved.
 *
 * These libraries and programs are free software; you can
 * redistribute them and/or modify them under the terms of the GNU
 * Lesser General Public License as published by the Free Software
 * Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * These libraries and programs are distributed in the hope that
 * they will be useful, but WITHOUT ANY WARRANTY; without even the
 * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
 * PURPOSE. See the GNU Lesser General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with these libraries and programs; if not, write
 * to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
 * Floor, Boston, MA 02110-1301 USA
 */

/*
 *	$XConsortium: vwr.c /main/3 1995/11/06 17:57:10 rswiston $
 *
 * @(#)vwr.c	1.21 16 Aug 1994
 *
 * 	RESTRICTED CONFIDENTIAL INFORMATION:
 *	
 *	The information in this document is subject to special
 *	restrictions in a confidential disclosure agreement between
 *	HP, IBM, Sun, USL, SCO and Univel.  Do not distribute this
 *	document outside HP, IBM, Sun, USL, SCO, or Univel without
 *	Sun's specific written approval.  This document and all copies
 *	and derivative works thereof must be returned or destroyed at
 *	Sun's request.
 *
 *	Copyright 1993 Sun Microsystems, Inc.  All rights reserved.
 *
 */


/*
 * browser.c
 * This file contains code to implement the generic object browser.
 * It should be standalone - it should not contain dependencies on
 * the App Builder.
 *
 * Currently, this is a direct port of the Devguide browser.
 *
 * This file will be modified (soon) for the new browser for the CDE 
 * App Builder.
 */
#ifndef _POSIX_SOURCE
#define _POSIX_SOURCE 1
#endif

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <X11/Xlib.h>
#include <X11/X.h>
#include <Xm/Xm.h>
#include <ab_private/vwr.h>

/*
 * Some internal functions.
static ViewerNode	*r_locate_node();
 */
static void		erase_node();
static int		child_of_selected_node();
static void		draw_icon();
static void		draw_arrow();

/*************************************
	ADT of the browser structure
 *************************************/

/*
 * Create the browser object.
 */
Vwr
vwr_create(
    VMethods	m
)
{
    Vwr	v;

    v = (Viewer *) calloc(1, sizeof(Viewer));

    v->obj_data = NULL;
    v->methods = m;
    v->ui_handle = NULL;
    v->properties = NULL;
    v->next = NULL;
    v->previous = NULL;

    /*
     * v->tree == root
     * v->current_tree == 'current' subtree
     */
    v->tree = v->current_tree = NULL;

    if (m)
    {
	(*m->init_prop)(v);
    }

    return v;
}


/*
 * Destroy the browser object.
 */
void
vwr_destroy(
    Vwr	v
)
{
    VMethods	m;

    m = v->methods;

    if (m && m->free_prop)
	(*m->free_prop)(v);

    free(v);
}


/*
 * Recursively destroy a tree and whatever below it.
 */
void
vwr_destroy_tree
(
    ViewerNode	*tree
)
{
    Viewer		*b;
    ViewerMethods	*m;
    ViewerNode	*child;
    ViewerNode	*next_child;
    int			i, num_child;

    if (!tree)
	return;

    b = tree->browser;
    m = b->methods;

    num_child = (*m->get_num_children)(tree);

    /*
     * Recursively destroy children subtrees
     * Destroy procs should check for (child == NULL) 
     */
    for (i=0, child = (*m->get_child)(tree, 0); 
            (i < num_child); 
            child = (*m->get_child)(tree, ++i))
        vwr_destroy_tree(child);

    if (tree == b->current_tree)
        b->current_tree = b->tree;

    vwr_destroy_node(tree);
}

VNode
vwr_create_node()
{
    VNode	bnode;

    bnode = (VNode) malloc(sizeof(ViewerNode));
    
    bnode->browser = NULL;
    bnode->obj_data = NULL;
    bnode->elm_methods = NULL;
    bnode->elements = NULL;
    bnode->num_elements = 0;
    bnode->boundbox_shown = TRUE;
    bnode->x = bnode->y = bnode->width = bnode->height = -1;
    bnode->state = 0;
    bnode->next = NULL;
    bnode->previous = NULL;

    return (bnode);
}


/*
 * Destroy one browser node.
 * To avoid mulitply destroying the same node, use this
 * function.
 */
void
vwr_destroy_node
(
    ViewerNode	*node
)
{
    free(node->elements);
    free(node);
}

void
vwr_init_elements(
    VNode	vnode
)
{
    Vwr		v;
    VMethods	m;
    
    if (!vnode)
	return;

    if (!(v = vnode->browser))
	return;

    if (!(m = v->methods))
	return;

    (*m->init_elements)(vnode);
}


/*
 * Reset the current tree to be the root of the tree.
 */
void
vwr_reset(Viewer *b)
{
	b->current_tree = b->tree;
}


/*
 * Delete a tree node from a given tree.
 */
void
vwr_delete_tree(Vwr v, VNode node)
{
}


/*
 * Traverse the given tree and apply the user-given function
 * to each of the nodes in the tree.
 * If the user-function (*fn) returns FALSE, the traversal
 * aborts.
 */
ViewerNode	*
vwr_traverse_tree
(
    ViewerNode		*tree,
    int			(*fn)(ViewerNode *)
)
{
    ViewerMethods	*m;
    ViewerNode	*child,
    			*rtn_node = NULL,
    			*tmp_node = NULL;
    int			i, num_child;

    if (!tree)
	return (NULL);

    m = BNODE_METHODS(tree);

    num_child = (*m->get_num_children)(tree);

    /*
     * Recursively traverse child subtrees
     * The traverse proc should check for (child == NULL) 
     */
    for (i=0, child = (*m->get_child)(tree, 0); 
            (i < num_child); 
            child = (*m->get_child)(tree, ++i))
    {
        if (tmp_node = vwr_traverse_tree(child, fn))
            rtn_node = tmp_node;
    }

    if ((*fn) (tree))
        return tree;
    else
        return rtn_node;
}

/**************************************************
	Drawing routines for the browser objects.
	PUBLIC FUNCTIONS.
 **************************************************/

#ifdef MATRIX
/*
 * Draw the tree in a matrix form.
 */
void
draw_matrix(b)
	Viewer	*b;
{
	ViewerNode	*node;

	if (!b->current_tree || !b->current_tree->first)
		return;

	recalc_matrix(b);
	for (node = b->current_tree->first; node; node = node->next) {
		render_matrix(b, node, XtWindow(b->canvas), b->select_fn);
	}
}
#endif


/**************************************************
	Drawing routines for the browser objects.
	PRIVATE FUNCTIONS.
 **************************************************/

#ifdef OLD
/*
 * Erase a node.
 */
static void
erase_node
(
    Viewer		*b,
    ViewerNode	*node,
    Pixmap		pixmap
)
{
    Widget	draw_area;

    draw_area = aob_ui_draw_area(b);

    XClearArea(XtDisplay(draw_area), XtWindow(draw_area), node->x, node->y, 
			node->width, node->height, FALSE);
}
#endif


#ifdef MATRIX
/*
 * Recalculate the coordinates of the nodes in a matrix.
 * b->select_fn is applied to each
 * of the nodes in the tree to determine if the node is to be
 * drawn.
 * After this function is called, some of the coordinates stored
 * in the tree node will be changed.  So to switch back to drawing
 * a tree, all the coordinates have to be recalculated. 
 * A temporary list is derived to temporarily link up all the
 * nodes that will be shown.
 */
static void
recalc_matrix(b)
	Viewer	*b;
{
	ViewerNode	*head = NULL;
	int	max_y, width;
	ViewerNode	*node;

	for (node = b->current_tree->last; node; node = node->previous)
		line_up_nodes(b, node, b->select_fn, &head);

	if (!head)
		return;

	XtVaGetValues(b->canvas, XtNwidth, &width, NULL);

	compute_matrix(b, head, X_ORIGIN, Y_ORIGIN, width, &max_y);
	reallocate_canvas(b->canvas, width, max_y);
}


/*
 * Line up the nodes in a linear list.  The next_in_line
 * pointer is used here.
 */
static void
line_up_nodes(b, tree, fn, next)
	Viewer	*b;
	ViewerNode	*tree;
	int	(*fn)();
	ViewerNode	**next;
{
	ViewerNode	*child;

	for (child = tree->last; child; child = child->previous)
		line_up_nodes(b, child, fn, next);

	if (!fn || (*fn) (b, tree)) {
		/*
		 * This is a node of concern.
		 */
		tree->next_in_line = *next;
		*next = tree;
	}
	else {
		tree->next_in_line = NULL;
		/*
		 * This is a node of no concern.  Put
		 * some irrelevant x, y coordinates in
	 	 * the node.
		 */
		tree->icon_x = -1 - tree->icon_width;
		tree->icon_y = -1 - tree->icon_height;
		tree->str_width = -1;
		tree->str_height = -1;
	}
}


/*
 * Compute the coordinates of the nodes if drawn in the
 * matrix form.
 * The tree data structure is good for drawing trees, but
 * bad for drawing matrices.  Well, what can I do?
 */
static void
compute_matrix(b, head, start_x, start_y, x_limit, end_y)
	Viewer	*b;
	ViewerNode	*head;
	int	start_x;
	int	start_y;
	int	x_limit;
	int	*end_y;
{	
	ViewerNode	*node;
	int	x = start_x;
	int	y = start_y;
	int	row_max_height = 0;
	
	for (node = head; node; node = node->next_in_line) 
	{
		browser_node_compute(node, 0, 0);
		if (x + node->width > x_limit) 
		{
			x = start_x;
			y = y + row_max_height + NODE_Y_GAP; 
			row_max_height = 0;
		}
		node->x = x;
		node->y = y; 
		node->icon_x = x + node->icon_x;
		node->icon_y = y + node->icon_y;
		node->str_x = x + node->str_x;
		node->str_y = y + node->str_y;
		x = x + node->width + NODE_X_GAP;
		if (node->height > row_max_height)
			row_max_height = node->height;
	}
	*end_y = y + row_max_height + NODE_Y_GAP;
}


/*
 * Actually draw the matrix of nodes on the screen.
 */
static void
render_matrix(b, tree, pixmap, fn)
	Viewer	*b;
	ViewerNode	*tree;
	Pixmap	pixmap;
	int	(*fn)();
{
	ViewerNode	*child;

	if (!fn || (*fn) (b, tree))
	{
		if (tree && tree->selected)
			browser_node_render(tree, pixmap, TRUE);
		else
			browser_node_render(tree, pixmap, FALSE);
	}

	for (child = tree->first; child; child = child->next)
		render_matrix(b, child, pixmap, fn);
}

#endif


/*
 * Given x and y, determine which node the point is in.
 */
VNode
vwr_locate_node
(
    Vwr		b,
    int		x,
    int		y
)
{
    VNode		selected_node = NULL;
    ViewerMethods	*m;

    if (!b)
	return(NULL);

    m = b->methods;

    /*
    selected_node = r_locate_node(b->current_tree, x, y);
    */
    selected_node = (*m->locate_node)(b->current_tree, x, y);

    return selected_node;
}

#ifdef OLD
/*
 * Draw the selection box for the selected icon.
 */
static void
draw_icon(pixmap, x, y, node)
	Pixmap	pixmap;
	int	x;
	int	y;
	ViewerNode	*node;
{

	int	x_delta = x - node->icon_x;
	int	y_delta = y - node->icon_y;
	XDrawRectangle(dpy, pixmap, xor_gc, 
			node->icon_x + x_delta + ICON_OFFSET,
			node->icon_y + y_delta, 
			node->icon_width - 2 * ICON_OFFSET, 
			node->icon_height);
}


/*
 * Draw an up-pointing arrow at the given point.
 */
static void
draw_arrow(pixmap, x, y)
	Pixmap	pixmap;
	int	x;
	int	y;
{
#define pcount 7

	int	i;
	XPoint plist[pcount];
	static XPoint list[] = {
		{-2, 10}, {-2, 6}, {-6, 6}, {0, 0}, {6, 6}, {2, 6}, {2, 10},
	};

	for (i = 0; i < pcount; i++)
	{
		plist[i] = list[i];
		plist[i].x += x;
		plist[i].y += y;
	}

	XFillPolygon(dpy, pixmap, xor_gc, plist, pcount, Convex, CoordModeOrigin);

#undef pcount
}

#endif

/*
 * vwr_num_cond()
 * Returns:
 *	- the nuber of nodes satisfying the passed 
 *	  condition/function
 */
void
vwr_num_cond(
    VNode	tree,
    int		*num,		/* RETURN */
    int		(*cond)(VNode)
)
{
    VMethods	m;
    VNode	child;
    int		i, 
		num_child;

    if (!tree || !num)
	return;

    m = BNODE_METHODS(tree);

    num_child = (*m->get_num_children)(tree);

    /*
     * Recursively count selected nodes of child subtree
     * This proc should check for (child == NULL) 
     */
    for (i=0, child = (*m->get_child)(tree, 0); 
            (i < num_child); 
            child = (*m->get_child)(tree, ++i))
    {
        vwr_num_cond(child, num, cond);
    }

    /*
     * Increment running total if:
     *	- there is no condition function
     *	- if the condition function returns a non zero
     *    result
     */
    if (!cond || (*cond)(tree))
    {
	++(*num);
    }
}

/*
 * vwr_get_cond()
 * Returns the nodes satisfying the passed condition/function
 * in the passed array of nodes.
 * Returns the number of nodes satisfying the condition in
 * 'num'.
 * Space for the array is allocated. This space must be free'd
 * by the caller of this function.
 */
void
vwr_get_cond(
    VNode	tree,
    VNode	**array,	/* RETURN */
    int		*num,		/* RETURN */
    int		(*cond)(VNode)
)
{
    VMethods	m;
    VNode	child;
    VNode	*cur_array = NULL,
    		*child_array;
    int		child_num,
    		cur_num = 0;
    int		i, 
		num_child;

    /*
     * Return immediately if a NULL root is passed or
     * the count pointer is NULL
     */
    if (!tree || !num)
	return;

    /*
     * Get the methods vector.
     * Return if it is NULL
     */
    if (!(m = BNODE_METHODS(tree)))
	return;

    /*
     * Get child count
     */
    num_child = (*m->get_num_children)(tree);

    /*
     * Search for child nodes satisfying condition first
     */
    for (i=0, child = (*m->get_child)(tree, 0); 
            (i < num_child); 
            child = (*m->get_child)(tree, ++i))
    {
	/*
	 * Reset child count, array
	 */
	child_num = 0;
	child_array = NULL;

	/*
	 * Get nodes from current child subtree satisfying 
	 * condition
	 */
        vwr_get_cond(child, &child_array, &child_num, cond);

	/*
	 * Any nodes satisfied condition ?
	 */
	if (child_num > 0)
	{
	    /*
	     * Have we found any nodes yet ?
	     * If we have,
	     *	cur_array = the array containing the nodes we
	     *      have found so far
	     *	cur_num = the current total
	     */
	    if (cur_num > 0)
	    {
	        VNode	*new_array;

		/*
		 * We need to allocate a bigger array
		 * and copy everything into it
		 */
	        new_array = (VNode *)calloc(sizeof(VNode), 
				cur_num + child_num);

		/*
		 * Copying the current nodes list
		 */
		memcpy(new_array, cur_array, 
			(cur_num * sizeof(VNode)));

		/*
		 * Copying the child nodes list
		 */
		memcpy(&new_array[cur_num], child_array, 
			(child_num * sizeof(VNode)));
		
		/*
		 * Free the old array AND
		 * the child array
		 */
		free((char *)cur_array);
		free((char *)child_array);

		/*
		 * Point current array to this new, 
		 * bigger array
		 */
		cur_array = new_array;

	    }
	    else
	    {
		/*
		 * If we have not found any nodes yet, make the 
		 * returned node the current node
		 */
		cur_array = child_array;

		/*
		 * Paranoia - make sure cur_num is not < 0
		 */
		cur_num = 0;
	    }

	    /*
	     * Increment current node count
	     */
	    cur_num += child_num;
	}

    }

    /*
     * Check if parent node satisfies condition
     */
    if (!cond || (*cond)(tree))
    {
        VNode	*new_array;

	/*
	 * Allocate new array to add one extra node
	 */
        new_array = (VNode *)calloc(sizeof(VNode), 
                                cur_num + 1);

	/*
	 * If we have nodes accumulated from before,
	 * copy those guys into this new array.
	 */
        if (cur_num > 0)
        {
            memcpy(new_array, cur_array, 
                    (cur_num * sizeof(VNode)));

	    /*
	     * Copy the parent node
	     */
            new_array[cur_num] = tree;

	    /*
	     * Free old array
	     */
            free((char *)cur_array);

	    /*
	     * Point current array to this new one
	     */
	    cur_array = new_array;

        }
        else
        {
	    /*
	     * No nodes accumulated so far
	     * Just copy parent node
	     */
            new_array[0] = tree;

	    cur_array = new_array;
        }

	/*
	 * Increment count
	 */
        ++cur_num;
    }

    /*
     * Return total number of nodes found and the array
     * of nodes
     */
    *num = cur_num;
    *array = cur_array;
}

/*
 * Repaint the viewer
 */
void
vwr_repaint
(
    Viewer	*v
)
{
    VMethods	m;

    if (!v || !v->current_tree)
        return;

    m = v->methods;

    (*m->render_tree)(v->current_tree);
}

