################################################################################
# TSL-LIBRARY:	EMOS_FRM_TBL_Lib
################################################################################
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# For further information please contact:
#
# Dean Rajovic
# mailto:drajovic@gmx.de
# http://sourceforge.net/projects/emos-framework
#
################################################################################
# $Revision: 1.8 $
# $Author: drajovic $
# $Date: 2005/01/28 11:18:43 $
# $Source: C:/Archive/FRAMEWORK/EMOS_GPL/FRM/emos_frm_tbl_lib/script,v $
# $NoKeywords: $
################################################################################

#/***
#* This libary provides a family of functions for support of table GUI objects.
#* GUI objects in table format are known for being difficult to handle for 
#* many reasons. First of all, although a single object a tables can
#* potentialy carry huge amounts of data. Depending on the testing objectives
#* we sometimes need to handle only a handfull of cells and in other cases big
#* portions of it if not the whole table. Programatic interfaces to tables are
#* usually handle individual table cells and the navigation through table. 
#* More complicated actions on larger parts of table data are left to test 
#* programmers to be designed from the basic table operations.<br>
#* Secondly, tables are often very dynamic objects (i.e. their content and their
#* strucure sometimes changes within a single test session). A robust test design 
#* should provide for easy location of data (e.g. dynamcally determining the 
#* appropriate row/column) which is not a trivial task.<br>
#* Finally, individual table cells can in fact contain various sorts of other
#* GUI objects such as edit fields, drop-down lists, combo boxes, radio buttons, 
#* check buttons and other. Sometimes it seems as if vendors of GUI toolkits 
#* simply had to invent something that would distinguish their tables from all
#* the other ones. 
#* <p>
#* WinRunner's tbl interface does a fairly good job in making these various 
#* tables appear similar. However, there are plenty of situations where this is
#* not the case. Here only individual solutions can help. Our table interface
#* builds upon the strengths of WinRunner tbl-interface and is therefore able to
#* handle many different tables. Still, due to mentioned difficulties, the
#* interface is limited in some aspects and will need to be tuned for certain 
#* environments. Our code should work well with HTML tables and with tables
#* with well-working tbl-interface (tbl_set_cell_data/tbl_get_cell_data).
#* We hope also, that our code provides enough design ideas which will help you
#* handle exotic cells such as edit fields, combo-boxes, etc.
#* <p>
#* Our table support has been specially designed for use in EMOS Framework tables.
#* Pay special attention to functions FRM_TBL_set_data() and FRM_TBL_set_data_block().
#* The former implements an interface for handing individual table cells.
#* The later implements an interface for handing bigger portions of tables.
#*
#* @author drajovic
#*/

#/**
#* Performs operations on single table cells for the whole test block (i.e.
#* loops through all cells of a particular test block until an empty cell
#* or the last cell is reached.
#*
#* @param table (in) table index
#* @param test (in) test name
#* @param obj (in) logical name or phisical description of the table object
#* @param desc (in) [optional] physical description of an edit object that is
#*  located in the particular table cell (attempt only if such object really
#*  appears in the cell; we had to fight one tweeked VB table once with this;
#*  hopefulla you'll never need it; see FRM_TBL_cell_aktion() for more)
#* @param list (in) [optional] physical description of a list object that is
#*  located in the particular table cell (attempt only if such object really
#*  appears in the cell, see code of FRM_TBL_cell_aktion function)
#* @return E_OK if successful, else error
#*/

public function FRM_TBL_set_data ( in table, in test, in obj, in desc, in list )
{
	auto val, mode, rc;
	auto rc2 = E_OK;
	# loop through test block until an empty cell or last cell is reached
	while( ( rc = FRM_get_next( table, test, val )) == E_OK )
	{
		wrlog_prim_start();
		FRM_log_frm_info( table, test, val );
		mode = FRM_get_mode( table );
		switch( mode )
		{
		case FRM_SET_MODE:
		case FRM_CHK_MODE:
			rc2+=FRM_TBL_cell_action( obj, val, mode, desc, list );
			break;
		case FRM_ATR_MODE:
		case FRM_GEN_MODE:
			# to be implemented
			break;
		}
		wrlog_prim_stop( rc2 );
	}
	if ( rc == E_FRM_SKIP || rc == E_OUT_OF_RANGE )  # empty or EOF
		rc = E_OK;
	# if one or the other rc variable <> 0, return error
	if ( rc2 != E_OK )
		return FRM_rc2( rc2, "FRM_TBL_cell_action" );
	return FRM_rc2( rc, "FRM_TBL_set_data" );
}

#/**
#* Performs an action(s) on a block of table cells.
#*
#* @param tid1	(in)	id of the table where the instructins come from
#* @param test	(in)	name of the test to run (as named in column "Name")
#* @param obj	(in)	table object where actions are to be performed
#*/

public function FRM_TBL_set_data_block ( in tid1, in test, in obj )
{
	auto rc, row, name, val, table2, tid2, dir, file, mode;

	rc+=FRM_get_current_row( tid1, row );
	while( rc == E_OK || rc == E_FRM_SKIP )
	{
		row++;
		rc = FRM_get( tid1, FRM_COL_NAME, name, row );
		if ( rc == E_FRM_SKIP )
		{
			rc = E_OK;
			break;
		}
		if ( rc != E_OK )
		{
			FRM_log_frm_info( tid1, test, obj );
			return FRM_rc2( rc, "FRM_TBL_set_data_block-> cannot retrieve cell: Name=["&name&"], row=["&row&"], rc=["&rc&"]" );
		}
		if ( match(name,"<<[Ee][Nn][Dd][Ee]*>>") )
			break;
		rc = FRM_get( tid1, test, val, row );
		if ( rc == E_FRM_SKIP ) continue;
		if ( rc != E_OK ) return rc;
		split_path( FRM_get_name( tid1 ), dir, file, "\\" );
		table2 = join_path( dir, name, "\\" );
		if ( !FRM_is_open( table2 ) )
		{
			# open table2 and load all columns
			rc = FRM_open( table2, "<<ALL>>", tid2 );
			if ( rc != E_OK )
			{
				FRM_log_frm_info( table2, tid2, obj );
				return FRM_rc2( rc, "FRM_TBL_set_data_block-> cannot open FRM table: Table=["&table2&"], RC=["&rc&"]" );
			}
			# table2 inherits the mode from table1
			rc = FRM_set_mode( tid2, FRM_get_mode( tid1 ) );
		}
		mode = FRM_get_mode( tid2 );
		switch( mode )
		{
		case FRM_SET_MODE:
		case FRM_CHK_MODE:
			rc = FRM_TBL_process_data_block( tid2, val, obj );
			break;
		case FRM_ATR_MODE:
		case FRM_GEN_MODE:
			#not implemented
			break;
		}	
	}
	return rc;
}

#/**
#* Process 2-dimensional table block
#*/

static function FRM_TBL_process_data_block ( in tid, in block, in obj )
{
	auto rc;
	auto x, y, data[];
	auto i, j;
	auto mode, val;
	auto sep, tmp[], cols[], count;
	auto action;
	auto msg;
	
	rc = FRM_get_block( tid, block, x, y, data );
	if ( rc != E_OK )
		return rc;

	mode = FRM_get_mode( tid );

	# parse column headers (they define separator, action, column name and, optionally, object description)
	for( i=1; i<x; i++ )
	{
		sep = substr( data[0,i], 1, 1 );
		count = split( data[0,i], tmp, sep );
		if ( count < 3 )
		{
			msg = sprintf( "%s: invalid format expected <sep>action<sep>column[<sep>object[<sep>exact]]", data[0,i] );
			FRM_log_frm_info( tid, block, msg );
			FRM_log_obj_info( obj );
			return E_ILLEGAL_PARAMETER;
		}
		cols[i,"sep"] = sep;
		cols[i,"act"] = tmp[2];
		cols[i,"col"] = tmp[3];
		# 'obj' & 'exact' are optional; will be set to "" automatically
		cols[i,"obj"] = tmp[4];
		cols[i,"exact"] = tmp[5];
	}
	# for all rows
	for( i=1; i<y; i++ )
	{
		# for all columns
		for( j=1; j<x; j++ )
		{
			val = data[i,j];
			if ( FRM_parse_val( tid, val ) == E_FRM_SKIP )
			{ 
				continue;
			}
			sep = cols[j,"sep"];
			action = sprintf( "%s%s%s%s%s%s%s%s%s%s%s%s", 
				sep, cols[j,"act"], 
				sep, data[i,0], 
				sep, cols[j,"col"], 
				sep, val, 
				sep, cols[j,"obj"], 
				sep, cols[j,"exact"] );
			rc+=FRM_TBL_cell_action( obj, action, mode );
		}
	}
	return rc;
}

######################################################################
# low-level interface
######################################################################

#/**
#* Holds the adjustment amount for the tbl_get_rows_count() function.
#* @see FRM_TBL_set_rows_count_adjustment()
#*/

static aut_tbl_rows_count_adjustment = 0;

#/**
#* Function tbl_get_rows_count() sometimes returns count that is less than the
#* physical number of rows. If present, the headings row is typically not counted.
#* So the returned count is one less than the physical count. For our search
#* functions (e.g. FRM_TBL_find_cell()) we need to access the headings row to 
#* search them. Increase the count_adjustment if you notice that our functions
#* do not seem to find the value in the last row.
#* 
#* @param amount	(in)	amount to adjust
#*/

public function FRM_TBL_set_rows_count_adjustment ( in amount )
{
	aut_tbl_rows_count_adjustment = amount;
}

#/**
#* Function tbl_get_rows_count() sometimes returns count that is less than the
#* physical number of rows. If present, the headings row is typically not counted.
#* So the returned count is one less than the physical count. For our search
#* functions (e.g. FRM_TBL_find_cell()) we need to access the headings row to 
#* search them. Increase the count_adjustment if you notice that our functions
#* do not seem to find the value in the last row.
#* 
#* @return the row adjustment amount
#*/

public function FRM_TBL_get_rows_count_adjustment ( )
{
	return aut_tbl_rows_count_adjustment;
}
#/**
#* Holds the adjustment amount for the tbl_get_cols_count() function.
#* @see FRM_TBL_set_cols_count_adjustment()
#*/

static aut_tbl_cols_count_adjustment = 0;

#/**
#* Function tbl_get_cols_count() sometimes returns count that is less than the
#* physical number of columns. If present, the headings column is typically not 
#* counted. So the returned count is one less than the physical count. For our 
#* search functions (e.g. FRM_TBL_find_cell()) we need to access the headings 
#* column to search for them. Increase the count_adjustment if you notice that 
#* our functions do not seem to find the value in the last column.
#* 
#* @param amount	(in)	amount to adjust
#*/

public function FRM_TBL_set_cols_count_adjustment ( in amount )
{
	aut_tbl_cols_count_adjustment = amount;
}

#/**
#* Function tbl_get_cols_count() sometimes returns count that is less than the
#* physical number of columns. If present, the headings column is typically not 
#* counted. So the returned count is one less than the physical count. For our 
#* search functions (e.g. FRM_TBL_find_cell()) we need to access the headings 
#* column to search for them. Increase the count_adjustment if you notice that 
#* our functions do not seem to find the value in the last column.
#* 
#* @return the row adjustment amount
#*/

public function FRM_TBL_get_cols_count_adjustment ( )
{
	return aut_tbl_cols_count_adjustment;
}

#/**
#* Performs the action on a table cell as specified by the command <code>cmd</code>.
#* There are four types of actions you can perform on a table cell:
#* <ul>
#*	<li>S = <b>s</b>elect the cell (i.e. tbl_set_selected_cell/tbl_activate_cell)</li>
#*	<li>E = <b>e</b>nter the value in a cell box of type edit (i.e. edit_set)</li>
#*	<li>P = <b>p</b>ick the value from a list (i.e. list_select_item)</li>
#*	<li>T = <b>t</b>ype into the cell (i.e. type)</li>
#*	<li>D = enter <b>d</b>ata using tbl functions such as tbl_set_cell_data() or 
#*		tbl_get_cell_data()</li>
#* </ul>
#*<p>
#* All actions can be specified by a generic syntax:
#* <p>
#* <pre>~action~[row]~[col][~val[~obj[~exact]]]</pre>
#* <p>
#* where<br>
#* <ul>
#* <li>~ = any character that occurs only at specified positions (separator)</li>
#* <li>action = one of the following: S|E|P|T|D, action can be prefixed with . or :
#* (e.g. .S) where . means selection (tbl_set_selected_cell) and : means
#* activation (tbl_activate_cell, i.e. double-click) [default: selection]</li>
#* <li>row = row specification either as 
#*		<ul>
#*		<li>index (e.g. <code>#1</code>)</li>
#*		<li>search expression in form <code>col1=val1[;coln=valn]...</code>
#*			where col1 ist the name of the column containing val1, etc. the
#*			separator ; (semicolon) is interpreted as logical AND</li>
#*		<li><code>&lt;LAST&gt;</code>  in which case the last valid row spec is
#*			reused (no search is performed for preformance reasons)</li>
#*		<li>name (e.g. <code>Par1</code>) it is assumed that col #0 contains row 
#*			names [obsolete; replaced by <code>#0=val</code>] </li>
#*		<li>in case of Selection row can be left empty (assuming col and val are 
#*			filled in) where val is searched for in col #0 [obsolete, replaced
#*			by construct <code>#0=val</code>]</li>
#*		</ul>
#* <li>col = column specification either as
#*		<ul>
#*		<li>index (e.g. <code>#1</code>)</li>
#*		<li>name (e.g. <code>Col1</code>)</li>
#*		<li>search expression in form <code>row1=val1[;rown=valn]...</code>
#*			where row1 ist the name of the row containing val1, etc. the
#*			separator ; (semicolon) is interpreted as logical AND</li>
#*		<li><code>&lt;LAST&gt;</code>  in which case the last valid column spec is
#*			reused (no search is performed for preformance reasons)</li>
#*		<li>in case of Selection col can be left empty (assuming row and val are 
#*			filled in) where val is searched for in row #0 [obsolete, replaced
#*			by construct <code>#0=val</code>]</li>
#*		</ul>
#* <li>val = value which can be interpreted two-fold<br>
#*	in case of Selection val can specify the content of the cell which is to be selected<br>
#*	in other cases val represents the value for the E|P|T|D operations</li>
#* <li>obj = a phisical description of the object to be actioned upon</li>
#* <li>exact = indicats whether exact or tollerant matching [default] is to be performed</li>
#* </ul>
#* <p>
#* A few examples:
#* <ul>
#* <li>~S~#1~#2<br>
#*	selects cell indexed by row=#1, col=#2<br><br></li>
#* <li>~.S~AnzahlH~Wert<br>
#*	selects cell in a row named "AnzahlH" and a column named "Wert"<br><br></li>
#* <li>~:S~~Wert~xxx<br>
#*	activates cell in a column "Wert" that contains "xxx"<br><br></li>
#* <li>~S~#1~~xxx<br>
#*	activates cell in a row #1 that contains "xxx"<br><br></li>
#* <li>~E~#1~Wert~xxx<br>
#*	sets (edit_set) cell in row #1 and column named "Wert" to "xxx"<br><br></li>
#* <li>~E~#1~#2~xxx~{class:edit,index:12}<br>
#*	sets (edit_set) object with the given description that appears in cell #1:#2 to "xxx"<br><br></li>
#* <li>~S~aaa~bbb~~~1<br>
#*	selects cell named exactly "aaa":"bbb"<br><br></li>
#* <li>~.T~#1~#2~abc<br>
#*	types "abc" in cell #1:#2 (after selecting a cell with a single click)<br><br></li>
#* </ul>
#* @param tbl	(in)	table name
#* @param cmd	(in)	command to be performed
#* @param frm_mode (in)	[optional] FRM mode (FRM_SET_MODE|FRM_CHK_MODE|FRM_ATR_MODE|FRM_GEN_MODE)
#* @param desc	(in) [optional] default physical description of the object to be acted upon
#* 		this description is used unless overridden by the cmd,
#*		if you don't provide this argument, "{class:edit}" will be used
#* @param list	(in) [optional] a description or a logical name of the combo box
#* 		containing the obj.
#*/

public function FRM_TBL_cell_action( in tbl, in cmd, in frm_mode, in desc, in list )
{
	extern RLENGTH;
	
	auto sep;	# separator
	auto typ;	# query type S=selection, E=edit, P=pick; T=type
	auto row;	# row# or row name
	auto col;	# col# or column name
	auto val;	# value to be set/selected
	auto obj;	# object where action is to be performed
	auto exact;	# indicator for exact or tollerant match [default: tollerant]

	auto simple_select;	# cell selection mechanism [default: double-click]
	
	auto arr[], count, i;
	auto r, c;
	auto w, h;
	auto msg, dummy, rc;

	if ( frm_mode == "" )
		frm_mode = FRM_SET_MODE;
			
	sep = substr( cmd, 1, 1 );
	count = split( cmd, arr, sep );
	
	if ( count < 4 )
		return E_ILLEGAL_PARAMETER;

	typ = toupper( arr[2] );
	row = arr[3];
	col = arr[4];
	# don't worry for these three (they will be created if not specified)
	val = arr[5];
	obj = arr[6];
	exact = arr[7];
	
	switch ( typ )
	{
	case "S":
	case "D":
	case "DCHK":
	case "E":
	case "B":
	case "BCHK":
	case "P":
	case "T":
	case ".S":
	case ".D":
	case ".DCHK":
	case ".E":
	case ".B":
	case ".BCHK":
	case ".P":
	case ".T":
		simple_select = TRUE;  # i.e. use tbl_set_selected_cell()
		break;
	case ":S":
	case ":D":
	case ":DCHK":
	case ":E":
	case ":B":
	case ":BCHK":
	case ":P":
	case ":T":
		simple_select = FALSE;  # i.e. use tbl_activate_cell()
		break;
	default:
		rc = E_ILLEGAL_PARAMETER;
		msg = sprintf( "%s: invalid type, valid options=S|E|P|T|D", typ );
		return FRM_rc2( msg, rc );
	}

	rc = FRM_TBL_determine_row( tbl, row, col, val, r, exact );
	if ( rc != E_OK )
		return FRM_rc2( rc, sprintf( "%s: could not determine row index", row ) );
	rc = FRM_TBL_determine_col( tbl, row, col, val, c, exact );
	if ( rc != E_OK )
		return FRM_rc2( rc, sprintf( "%s: could not determine column index", col ) );

	# watch the trick !!! object is created dynamically!
	# there are two ways to identify the object:
	#   1. pass the description via parameter 
	#      (this way you define the default object)
	#   2. pass the desciption via command 
	#      (i.e. overide the default by anything you want from a data table)
	# if you don't specify anything, we'll try with an edit object

	if ( arr[6] == "" )
		obj = desc;
	if ( match( obj, " *web:" ) == 1 )
		obj = FRM_TBL_web_get_child_item( tbl, r, c, obj );
	if ( obj == "" )
		obj = "{class:edit}";

	switch ( typ )
	{
	case "S":		# Selection
	case ":S":
	case ".S":
		switch( frm_mode ) 
		{
		case FRM_SET_MODE:
		case FRM_CHK_MODE:
		case FRM_ATR_MODE:
		case FRM_GEN_MODE:
			rc+=FRM_TBL_select( tbl, r, c, simple_select );
			break;
		}
		break;
	case "D":		# Data = tbl functions
	case ":D":
	case ".D":
		switch( frm_mode ) 
		{
		case FRM_SET_MODE:
			rc+=FRM_TBL_SET_data( tbl, r, c, val, simple_select );
			break;
		case FRM_CHK_MODE:
			rc+=FRM_TBL_CHK_data( tbl, r, c, val, simple_select, exact );
			break;
		case FRM_ATR_MODE:
		case FRM_GEN_MODE:
			break;
		}
		break;
	case "DCHK":		# Data = tbl functions (force check)
	case ":DCHK":
	case ".DCHK":
		switch( frm_mode ) 
		{
		case FRM_SET_MODE:
		case FRM_CHK_MODE:
			rc+=FRM_TBL_CHK_data( tbl, r, c, val, simple_select, exact );
			break;
		case FRM_ATR_MODE:
		case FRM_GEN_MODE:
			break;
		}
		break;
	case "E":		# Edit = edit field
	case ":E":
	case ".E":
		switch( frm_mode ) 
		{
		case FRM_SET_MODE:
			rc+=FRM_TBL_SET_edit( tbl, r, c, obj, val, simple_select, exact );
			break;
		case FRM_CHK_MODE:
			rc+=FRM_TBL_CHK_edit( tbl, r, c, obj, val, simple_select, exact );
			break;
		case FRM_ATR_MODE:
		case FRM_GEN_MODE:
			break;
		}
		break;
	case "B":		# Button = radio button or check box
	case ":B":
	case ".B":
		switch( frm_mode ) 
		{
		case FRM_SET_MODE:
			rc+=FRM_TBL_SET_button( tbl, r, c, obj, val, simple_select );
			break;
		case FRM_CHK_MODE:
			rc+=FRM_TBL_CHK_button( tbl, r, c, obj, val, simple_select );
			break;
		case FRM_ATR_MODE:
		case FRM_GEN_MODE:
			break;
		}
		break;
	case "BCHK":		# Button = radio button or check box (force check)
	case ":BCHK":
	case ".BCHK":
		switch( frm_mode ) 
		{
		case FRM_SET_MODE:
		case FRM_CHK_MODE:
			rc+=FRM_TBL_CHK_button( tbl, r, c, obj, val, simple_select );
			break;
		case FRM_ATR_MODE:
		case FRM_GEN_MODE:
			break;
		}
		break;
	case "P":		# Pick = drop-down list
	case ":P":
	case ".P":
		switch( frm_mode ) 
		{
		case FRM_SET_MODE:
			rc+=FRM_TBL_SET_pick( tbl, r, c, obj, list, val, simple_select );
			break;
		case FRM_CHK_MODE:
			rc+=FRM_TBL_CHK_pick( tbl, r, c, obj, list, val, simple_select, exact );
			break;
		case FRM_ATR_MODE:
		case FRM_GEN_MODE:
			break;
		}
		break;
	case "T":		# Type = anything
	case ":T":
	case ".T":
		switch( frm_mode ) 
		{
		case FRM_SET_MODE:
			rc+=FRM_TBL_type( tbl, r, c, val, simple_select );
			break;
		case FRM_CHK_MODE:
		case FRM_ATR_MODE:
		case FRM_GEN_MODE:
			break;
		}
		break;
	}
	return rc;
}

public function FRM_TBL_CHK_cell_action( in tbl, in cmd, in desc, in list )
{
	extern RLENGTH;
	
	auto sep;	# separator
	auto typ;	# query type S=selection, E=edit, P=pick; T=type
	auto row;	# row# or row name
	auto col;	# col# or column name
	auto val;	# value to be set/selected
	auto obj;	# object where action is to be performed
	auto exact;	# indicator for exact or tollerant match [default: tollerant]

	auto simple_select;	# cell selection mechanism [default: double-click]
	
	auto arr[], count, i;
	auto r, c;
	auto w, h;
	auto msg, dummy, rc;
	auto val2;

	sep = substr( cmd, 1, 1 );
	count = split( cmd, arr, sep );
	
	if ( count < 4 )
		return E_ILLEGAL_PARAMETER;

	typ = toupper( arr[2] );
	row = arr[3];
	col = arr[4];
	# don't worry for these three (they will be created if not specified)
	val = arr[5];
	obj = arr[6];
	exact = arr[7];
	
	rc = FRM_TBL_determine_row( tbl, row, col, val, r, exact );
	if ( rc != E_OK )
		return rc;
	rc = FRM_TBL_determine_col( tbl, row, col, val, c, exact );
	if ( rc != E_OK )
		return rc;

	if ( arr[6] == "" )
	{
		obj = desc;
	}
	if ( obj == "" )
	{
		obj = "{class:edit}";
	}
	rc+=tbl_set_selected_cell( tbl, r, c );
	rc+=tbl_get_cell_data ( tbl, r, c, val2 );
	val2=strip_both ( val2, " " );
	if ( match( val2, val ) && length(val2) == RLENGTH )
	{
		FRM_log_obj_info( obj );
		tl_step( "FRM_TBL_CHK_cell_action", E_OK, "expected: \""&val&"\" actual: \""&val2&"\"" );
		rc = E_OK;
	}
	else
	{
		FRM_log_obj_info( obj );
		rc = FRM_rc2( E_DIFF, "expected: \""&val&"\" actual: \""&val2&"\"" );
	}
	return rc;
}

public function FRM_TBL_select ( in tbl, in row, in col, in simple_select )
{
	auto rc;
	if ( simple_select )
		rc+=tbl_set_selected_cell( tbl, "#"&row, "#"&col );
		# if the above does not work, try this
		#rc = tbl_click_cell( tbl, row, col );
	else
		rc = tbl_activate_cell( tbl, row, col );
	
	# some apps resize the grid when a cell gets selected
	# with this wait we hope to have stable grid before continuing	
	#wait( 0, 500 );
	return FRM_rc2( rc, sprintf("row=%s, col=%s", row, col) );
}

public function FRM_TBL_GET_data ( in tbl, in row, in col, out val, in simple_select )
{
	auto rc;
#	rc =FRM_TBL_select ( tbl, row, col, simple_select );
	rc =tbl_get_cell_data( tbl, row, col, val);
	return FRM_rc2( rc, sprintf("row=%s, col=%s, val=%s", row, col, val) );
}

public function FRM_TBL_SET_data ( in tbl, in row, in col, in val, in simple_select )
{
	auto rc;
#	rc =FRM_TBL_select ( tbl, row, col, simple_select );
	rc =FRM_rc2( tbl_set_cell_data( tbl, row, col, val), sprintf("row=%s, col=%s, val=%s", row, col, val) );
	# try the following line if you need to provoke the focus lost event
	#type( "<kReturn>" );
	return rc;
}

public function FRM_TBL_CHK_data ( in tbl, in row, in col, in val, in simple_select, in operation )
{
	extern RLENGTH;
	auto data;
	auto rc;
#	rc =FRM_TBL_select ( tbl, row, col, simple_select );
	rc+=FRM_rc2( tbl_get_cell_data( tbl, row, col, data), sprintf("row=%s, col=%s, val=%s", row, col, data) );
	if ( rc == E_OK )
	{
		rc = FRM_TBL_generic_check( data, val, operation );
 	}
	return rc;
}

static function FRM_TBL_generic_check ( in actual, in expected, in operation )
{
	extern RLENGTH;
	auto msg, rc;
	msg = sprintf( "expected=\"%s\", actual=\"%s\", operation=%s", expected, actual, operation );
	switch ( toupper(operation) ) 
	{
	case "":				# wenn nichts spezifiziert wird MATCH als default genommen
	case "MATCH":
		rc = match( actual, expected );
		break;
	case "EXACT":
		rc = match( actual, expected ) && RLENGTH == length( actual );
		break;
	case "==":
	case "EQ":
		rc = ( expected == actual );
		break;
	case "!=":
	case "NE":
		rc = ( expected != actual );
		break;
	case "<":
	case "LT":
		rc = ( expected > actual );
		break;
	case "<=":
	case "LE":
		rc = ( expected >= actual );
		break;
	case ">":
	case "GT":
		rc = ( expected < actual );
		break;
	case ">=":
	case "GE":
		rc = ( expected <= actual );
		break;
	default:
		return FRM_rc2( E_ILLEGAL_PARAMETER, operation & ": illegal operation" );
	}
	return FRM_rc2( (rc ? E_OK : E_MISMATCH), msg );
}

# edit

public function FRM_TBL_SET_edit ( in tbl, in row, in col, in obj, in val, in simple_select )
{
	auto rc;
	FRM_log_obj_info( obj );
	rc =FRM_TBL_select ( tbl, row, col, simple_select );
	rc+=edit_set( obj, val );
	#type( "<kReturn>" );
	return FRM_rc2( rc, val );
}

public function FRM_TBL_CHK_edit ( in tbl, in row, in col, in obj, in val, in simple_select, in exact )
{
	auto rc;
	FRM_log_obj_info( obj );
	rc =FRM_TBL_select ( tbl, row, col, simple_select );
	rc+=edit_check_text( obj, val, exact );
	return FRM_rc2( rc, val );
}

# button (radio button or check box)

public function FRM_TBL_SET_button ( in tbl, in row, in col, in obj, in val, in simple_select )
{
	auto rc;
	FRM_log_obj_info( obj );
	#rc =FRM_TBL_select ( tbl, row, col, simple_select );
	rc = button_set( obj, val );
	return FRM_rc2( rc, val );
}

public function FRM_TBL_CHK_button ( in tbl, in row, in col, in obj, in val, in simple_select, in exact )
{
	auto rc;
	FRM_log_obj_info( obj );
	#rc =FRM_TBL_select ( tbl, row, col, simple_select );
	rc= button_check_state( obj, val );
	return FRM_rc2( rc, val );
}

# pick

public function FRM_TBL_SET_pick ( in tbl, in row, in col, in obj, in list, in val, in simple_select )
{
	auto rc;
	FRM_log_obj_info( list );
	rc =FRM_TBL_select ( tbl, row, col, simple_select );
	rc+=FRM_TBL_list_select_item( obj, list, val );
	return FRM_rc2( rc, val );
}

public function FRM_TBL_CHK_pick ( in tbl, in row, in col, in obj, in list, in val, in simple_select, in exact )
{
	auto rc;
	FRM_log_obj_info( list );
	rc = FRM_TBL_select ( tbl, row, col, simple_select );
	# to be implemented
	return FRM_rc2( rc, val );
}

# type

public function FRM_TBL_type ( in tbl, in row, in col, in val, in simple_select )
{
	auto rc;
	rc = FRM_TBL_select ( tbl, row, col, simple_select );
	type( val );
	return FRM_rc2( rc, val );
}

################################################################################

#/**
#* Conains the value of the last selected  row 
#* (set/reset by the function FRM_TBL_determine_row())
#*/
static LAST_ROW;

#/**
#* Returns the name/index of the row depending on the specified row/col.
#* If &lt;row&gt; is given (i.e. != ""), the unchanged value is returned.
#* If &lt;row&gt; is not given (i.e. == ""), the row containing the given
#* &lt;val&gt; is searched for in the column specified by &lt;col&gt;.
#* If both &lt;row&gt; and &lt;col&gt; are not given, an error is returned.
#* @param tbl (in) table object
#* @param row (in) row to select (if row does not equal "", no search is
#*	performed; if row equals "<LAST>", last valid row determined by this function
#*	is returned (no search performed))
#* @param col (in) column where &lt;val&gt; is searched for
#* @param val (in) value to search for in the given column
#* @param out_row (out) the name of the determined row
#* @param exact (in) [optinal] true indicates that an exact match is to be performed
#* @return
#*	E_OK: value was found (i.e. out_row contains the name of the row)
#*	else: error
#*/

public function FRM_TBL_determine_row ( in tbl, in row, in col, in val, out out_row, in exact )
{
	auto c, dummy, rc;
	extern RLENGTH;

	# ... by searching for the <val> in <col>
	if ( row == "" )
	{
		if ( col == "" )
		{
			return E_ILLEGAL_PARAMETER;  # at least one dimension must be specified
		}
#		rc = FRM_TBL_find_cell( tbl, val, out_row, dummy, "", col, exact );
		rc = FRM_TBL_find_row( tbl, val, out_row );
		if ( rc != E_OK )
		{
			LAST_ROW = "";
			return rc;
		}
		LAST_ROW = out_row;
		return E_OK;
	}

	# ... by index (nothing to search for)
	if ( match( row, "#[0-9][0-9]*" ) && RLENGTH == length( row ) )  
	{
		out_row = row;
		LAST_ROW = row;
		return E_OK;
	}

	# ... by "<LAST>" (last valid row)
	if ( match( row, "<[Ll][Aa][Ss][Tt]>" ) && RLENGTH == length( row ) )  
	{
		debug_msg( "using last row: \"" & LAST_ROW & "\"" );
		out_row = LAST_ROW;
		return E_OK;
	}

	# ... by row name (search in column 0)
#	rc = FRM_TBL_find_cell( tbl, row, out_row, dummy, "", "#0", exact );
	rc = FRM_TBL_find_row( tbl, row, out_row );
	if ( rc != E_OK )
	{
		LAST_ROW = "";
		return rc;
	}
	LAST_ROW = out_row;
	return E_OK;
}

#/**
#* Conains the value of the last selected  col 
#* (set/reset by the function FRM_TBL_determine_col())
#*/
static LAST_COL;

#/**
#* Returns the name/index of the column depending on the specified row/col.
#* If &lt;col&gt; is given (i.e. != ""), the unchanged value is returned.
#* If &lt;col&gt; is not given (i.e. == ""), the column containing the given
#* &lt;val&gt; is searched for in the row specified by &lt;row&gt;.
#* If both &lt;row&gt; and &lt;col&gt; are not given, an error is returned.
#* @param tbl (in) table object
#* @param row (in) row to select (if row does not match "#[0-9][0-9]*", then
#*	column #0 is searched for the value &lt;row&gt;
#* @param col (in) column to select (if col does not equal "", no search is
#*	performed;  if row equals "<LAST>", last valid row determined by this function
#*	is returned (no search performed))
#* @param val (in) value to search for in the determined row
#* @param out_col (out) the name of the determined column
#* @param exact (in) [optinal] true indicates that an exact match is to be performed
#* @return
#*	E_OK: value was found (i.e. out_col contains the name of the column)
#*	else: error
#*/

public function FRM_TBL_determine_col ( in tbl, in row, in col, in val, out out_col, in exact )
{
	auto r, dummy, rc;
	extern RLENGTH;

	# ... by searching for the <val> in <row>
	if ( col == "" )
	{
		if ( row == "" )
		{
			return E_ILLEGAL_PARAMETER;  # at least one dimension must be specified
		}
		if ( match( row, "#[0-9][0-9]*" ) && RLENGTH == length( row ) )  
		{
			r = row;
		}
		else
		{	# search the <row> by its name in col 0
			rc = FRM_TBL_find_cell( tbl, row, r, dummy, "", "#0", exact );
			if ( rc != E_OK )
			{
				LAST_COL = "";
				return rc;
			}
		}
		rc = FRM_TBL_find_cell( tbl, val, dummy, out_col, r, "", exact );
		if ( rc != E_OK )
		{
			LAST_COL = "";
			return rc;
		}
		LAST_COL = out_col;
		return E_OK;
	}

	# ... by "<LAST>" (last valid col)
	if ( match( col, "<[Ll][Aa][Ss][Tt]>" ) && RLENGTH == length( col ) )  
	{
		debug_msg( "using last col: \"" & LAST_COL & "\"" );
		out_col = LAST_COL;
		return E_OK;
	}

	# ... by column index or name (nothing to search for)
	out_col = col;
	LAST_COL = col;
	return E_OK;
}

#/**
#* Searches for a row that contains given data in specified coumns. Note
#* that an exact mtch is always performed. 
#* If such row is found (first from top!) the row index is returned otherwise
#* an error is indicated. Cells can be specified using the following syntax:
#* <p>
#*	<pre>col=val[;col=val]...</pre>
#* <p>
#*	where <code>col</code> is either
#* <ul>
#*	<li><b>name</b> such as "Column1" (without quotes!)</li>
#*	<li><b>index</b> such as #3</li>
#* </ul> 
#* @param tbl(in) table to search
#* @param row_spec (in) formated cell content specification (see above)
#* @param out_row (out) row index (only if retrn == E_OK)
#* @param idx (in) [optional] internal index needed for recursion
#* @return
#*	E_OK:			row found
#*	E_NOT_FOUND:	row not found
#*	else:			other error
#*/

public function FRM_TBL_find_row( in tbl, in row_spec, out out_row, in idx, in row_offset )
{
	auto colArr[], colCount;
	auto col2Arr[], col2Count;
	auto rc, dummy;

	colCount = split( row_spec, colArr, ";" );
	if ( colCount < 1 )
		return E_NOT_FOUND;				# no row spec
	# initialise offsets (needed for recursion)
	if ( idx == "" )
		idx = 1;
	if ( row_offset == "" )
		row_offset = 1;
	# everything found! (recursion completed)
	if ( idx > colCount )
		return E_OK;

	col2Count = split( colArr[idx], col2Arr, "=" );
	if ( col2Count != 2 )
		return E_ILLEGAL_PARAMETER;		# invalid row spec

	if ( idx == 1 )
	{
		# vertically search only the first spec
		while ( (rc = FRM_TBL_find_cell( tbl, col2Arr[2], out_row, dummy, "", col2Arr[1], TRUE, row_offset ) ) == E_OK )
		{	# found the first one, now find the others
			row_offset = substr( out_row, 2 );
			rc = FRM_TBL_find_row( tbl, row_spec, dummy, ++idx, row_offset );
			if ( rc == E_OK )
				return rc;
			row_offset++;
		}
		return rc;
	}
	# recursive part
	# search for other colums is limmited only to curent row
	rc = FRM_TBL_find_cell( tbl, col2Arr[2], out_row, dummy, "#"&row_offset, col2Arr[1], TRUE, row_offset );
	if ( rc == E_OK )
		return FRM_TBL_find_row( tbl, row_spec, dummy, ++idx, row_offset);
	return rc;
	
}

#/**
#* Searches for a cell that contains a given <code>regex</code>. 
#* You can limit the search to a particular <code>row</code> or a <code>col</code>,
#* by providing their names or indices (#idx).
#*
#* @param tbl (in)	table object
#* @param regex (in)	text to search for (regular expression)
#* @param out_row (out)	row where text was found
#* @param out_col (out)	column where text was found
#* @param row (in) [optional] limit the search to this row only
#* @param col (in) [optional] limit the search to this column only
#* @param exact (in) [optional] TRUE=exact match, FALSE=tolerant match [default=FALSE]
#* @param row_offset (in) [optional] row index where the search begins [default=0]
#* @return
#*	E_OK:			text found
#*	E_NOT_FOUND:	text not found
#*	else:			other error
#*/

public function FRM_TBL_find_cell( in tbl, in regex, out out_row, out out_col, in row, in col, in exact, in row_offset )
{
	extern RLENGTH;
	
	auto rows, cols;
	auto offset = (row_offset == "" ? 1 : row_offset);
	auto val;
	auto rc;
	
	rc+=tbl_get_rows_count( tbl, rows );
	rc+=tbl_get_cols_count( tbl, cols );
	if ( rc )
		return rc;

	rows += FRM_TBL_get_rows_count_adjustment(); 
	cols += FRM_TBL_get_cols_count_adjustment(); 

	if ( int(row) > rows || int(col) > cols )
		return E_ILLEGAL_PARAMETER;
	# search the whole table (left-to-right, top-to-botom)
	if ( row == "" && col == "" )
	{
		for ( out_row=offset; out_row<rows; out_row++ )
		{
			for ( out_col=0; out_col<cols; out_col++ )
			{
				rc = tbl_get_cell_data( tbl, "#"&out_row, "#"&out_col, val );
				if ( rc ) return rc;
				if ( match( val, regex ) )
				{
					if ( !exact || RLENGTH == length( val ) )
					{
						out_row = "#"&out_row;
						out_col = "#"&out_col;
						return E_OK;
					}
				}
			}
		}
		return E_NOT_FOUND;
	}
	# search within a column
	if ( row == "" && col != "" )
	{
		out_col = col;
		for ( out_row=offset; out_row<rows; out_row++ )
		{
			rc = tbl_get_cell_data( tbl, "#"&out_row, out_col, val );
			if ( rc ) return rc;
			if ( match( val, regex ) )
			{
				if ( !exact || RLENGTH == length( val ) )
				{
					out_row = "#"&out_row;
					return E_OK;
				}
			}
		}
		return E_NOT_FOUND;
	}
	# search within a row
	if ( row != "" && col == "" )
	{
		out_row = row;
		for ( out_col=0; out_col<cols; out_col++ )
		{
			rc = tbl_get_cell_data( tbl, out_row, "#"&out_col, val );
			if ( rc ) return rc;
			if ( match( val, regex ) )
			{
				if ( !exact || RLENGTH == length( val ) )
				{
					out_col = "#"&out_col;
					return E_OK;
				}
			}
		}
		return E_NOT_FOUND;
	}
	# check the single cell
	out_row = row;
	out_col = col;
	rc = tbl_get_cell_data( tbl, out_row, out_col, val );
	if ( rc ) return rc;
	if ( match( val, regex ) )
	{
		if ( !exact || RLENGTH == length( val ) )
			return E_OK;
	}
	return E_NOT_FOUND;
}

#/**
#* Selects the cell and opens it's popup menue via the &lt;kAppa&gt; key.
#* @param tbl (in) table object
#* @param row (in) row to select
#* @param col (in) column to select
#*/

public function FRM_TBL_popup_cell_menu( in tbl, in row, in col )
{
	auto rc;

	rc = FRM_TBL_select_cell( tbl, row, col );
	if ( rc != E_OK )
		return rc;
	# press the popup menu key
	return obj_type( tbl ,"<kApps>" );
}

#/**
#* Opens a popup menue at the specified location within the table object
#*	or, if not specified, at the top-left corner of the table object.
#* @param tbl (in) table object
#* @param x (in) [optional] x-coordinate to click to [default: 0]
#* @param y (in) [optional] y-coordinate to click to [default: 0]
#*/

public function FRM_TBL_popup_menu( in tbl, in x, in y )
{
	return obj_mouse_click( tbl, x*1, y*1, RIGHT );
}

#/**
#* Pops a drop-down list on a Stingray-ComboBox-cell by performing
#* a left-mouse click at some calculated position within the <code>obj</code>.
#* 
#* @param obj	(in)	object to click
#* @param list	(in)	list object that drops down after the click
#* @param item	(in)	item to select
#* @return
#*	E_OK:	success
#*	else:	failure
#*/

public function FRM_TBL_list_select_item ( in obj, in list, in item )
{
	auto rc;
	auto w, h;

	rc+=FRM_TBL_obj_drop_list( obj );
	rc+=list_select_item( list, item );
	return rc;
}

#/**
#* Pops a drop-down list on Stingray-ComboBox-cell by performing
#* a left-mouse click at some calculated position within the <code>obj</code>.
#* 
#* @param obj	(in)	object to click
#* @return
#*	E_OK:	success
#*	else:	failure
#*/

public function FRM_TBL_obj_drop_list ( in obj )
{
	auto rc;
	auto x,y, w, h;
	#printf( "%s", obj );
	rc+=obj_get_info( obj, "abs_x", x );
	rc+=obj_get_info( obj, "abs_y", y );
	rc+=obj_get_info( obj, "width", w );
	rc+=obj_get_info( obj, "height", h );
	#printf( "Before Clicking %d", rc );
#	rc+=obj_mouse_click( obj, w-11, h/2, LEFT );
	rc+=move_locator_abs( x+w+5, y+(h/2) );
	rc+=click("Left");
	#printf( "After Clicking %d", rc );
	return rc;
}

public function FRM_TBL_web_get_child_item( in tbl, in row, in col, in desc )
{
	auto object;
	auto itemArr[];
	auto count;
	auto rc;
	
	count = split( desc, itemArr, ":" );
	if ( count != 3 )
	{
		return E_ILLEGAL_PARAMETER;
	}
	rc = web_obj_get_child_item ( tbl, row, col, itemArr[2], itemArr[3], object );
	if ( rc == E_OK )
		return object;
	else
		return rc;
}