summaryrefslogtreecommitdiffstats
path: root/doc/flush.n
diff options
context:
space:
mode:
authordkf <donal.k.fellows@manchester.ac.uk>2004-10-27 12:53:22 (GMT)
committerdkf <donal.k.fellows@manchester.ac.uk>2004-10-27 12:53:22 (GMT)
commit4c2d0f20bfa9108949678cf49bfdc58eedc7bb93 (patch)
treeb3ce80d2f183dc1bd02185c2bf44738b4377c9ed /doc/flush.n
parentcd7e7ec95e76bcb5bf66d7cc9e6d60aad70dba07 (diff)
downloadtcl-4c2d0f20bfa9108949678cf49bfdc58eedc7bb93.zip
tcl-4c2d0f20bfa9108949678cf49bfdc58eedc7bb93.tar.gz
tcl-4c2d0f20bfa9108949678cf49bfdc58eedc7bb93.tar.bz2
More minor doc fixes
Diffstat (limited to 'doc/flush.n')
-rw-r--r--doc/flush.n4
1 files changed, 2 insertions, 2 deletions
diff --git a/doc/flush.n b/doc/flush.n
index 0bb308c..fa009c2 100644
--- a/doc/flush.n
+++ b/doc/flush.n
@@ -5,7 +5,7 @@
'\" See the file "license.terms" for information on usage and redistribution
'\" of this file, and for a DISCLAIMER OF ALL WARRANTIES.
'\"
-'\" RCS: @(#) $Id: flush.n,v 1.5 2004/05/27 15:40:07 dkf Exp $
+'\" RCS: @(#) $Id: flush.n,v 1.6 2004/10/27 12:53:22 dkf Exp $
'\"
.so man.macros
.TH flush n 7.5 Tcl "Tcl Built-In Commands"
@@ -38,7 +38,7 @@ underlying file or device is able to absorb it.
Prompt for the user to type some information in on the console:
.CS
puts -nonewline "Please type your name: "
-flush stdout
+\fBflush\fR stdout
gets stdin name
puts "Hello there, $name!"
.CE
EXTERN Blt_ChainLink Blt_Chain_AllocLink(size_t size); +BLT_EXTERN Blt_ChainLink Blt_Chain_Append(Blt_Chain chain, + ClientData clientData); +BLT_EXTERN Blt_ChainLink Blt_Chain_Prepend(Blt_Chain chain, + ClientData clientData); +BLT_EXTERN void Blt_Chain_Reset(Blt_Chain chain); +BLT_EXTERN void Blt_Chain_InitLink(Blt_ChainLink link); +BLT_EXTERN void Blt_Chain_LinkAfter(Blt_Chain chain, Blt_ChainLink link, + Blt_ChainLink after); +BLT_EXTERN void Blt_Chain_LinkBefore(Blt_Chain chain, Blt_ChainLink link, + Blt_ChainLink before); +BLT_EXTERN void Blt_Chain_UnlinkLink(Blt_Chain chain, Blt_ChainLink link); +BLT_EXTERN void Blt_Chain_DeleteLink(Blt_Chain chain, Blt_ChainLink link); +BLT_EXTERN Blt_ChainLink Blt_Chain_GetNthLink(Blt_Chain chain, long position); +BLT_EXTERN void Blt_Chain_Sort(Blt_Chain chain, Blt_ChainCompareProc *proc); +BLT_EXTERN int Blt_Chain_IsBefore(Blt_ChainLink first, Blt_ChainLink last); + +#define Blt_Chain_GetLength(c) (((c) == NULL) ? 0 : (c)->nLinks) +#define Blt_Chain_FirstLink(c) (((c) == NULL) ? NULL : (c)->head) +#define Blt_Chain_LastLink(c) (((c) == NULL) ? NULL : (c)->tail) +#define Blt_Chain_PrevLink(l) ((l)->prev) +#define Blt_Chain_NextLink(l) ((l)->next) +#define Blt_Chain_GetValue(l) ((l)->clientData) +#define Blt_Chain_FirstValue(c) (((c)->head == NULL) ? NULL : (c)->head->clientData) +#define Blt_Chain_SetValue(l, value) ((l)->clientData = (ClientData)(value)) +#define Blt_Chain_AppendLink(c, l) \ + (Blt_Chain_LinkAfter((c), (l), (Blt_ChainLink)NULL)) +#define Blt_Chain_PrependLink(c, l) \ + (Blt_Chain_LinkBefore((c), (l), (Blt_ChainLink)NULL)) + +#endif /* _BLT_CHAIN_H */ diff --git a/src/bltConfig.C b/src/bltConfig.C new file mode 100644 index 0000000..cf67a23 --- /dev/null +++ b/src/bltConfig.C @@ -0,0 +1,2493 @@ + +/* + * bltConfig.c -- + * + * This file contains a Tcl_Obj based replacement for the widget + * configuration functions in Tk. + * + * Copyright (c) 1990-1994 The Regents of the University of California. + * Copyright (c) 1994-1997 Sun Microsystems, Inc. + * + * See the file "license.terms" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + * + * Copyright 2003-2004 George A Howlett. + * + * 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 AUTHORS + * OR COPYRIGHT HOLDERS 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. + */ + +/* + * This is a Tcl_Obj based replacement for the widget configuration + * functions in Tk. + * + * What not use the new Tk_Option interface? + * + * There were design changes in the new Tk_Option interface that + * make it unwieldy. + * + * o You have to dynamically allocate, store, and deallocate + * your option table. + * o The Tk_FreeConfigOptions routine requires a tkwin argument. + * Unfortunately, most widgets save the display pointer and + * de-reference their tkwin when the window is destroyed. + * o There's no TK_CONFIG_CUSTOM functionality. This means that + * save special options must be saved as strings by + * Tk_ConfigureWidget and processed later, thus losing the + * benefits of Tcl_Objs. It also make error handling + * problematic, since you don't pick up certain errors like + * + * .widget configure -myoption bad -myoption good + * + * You will never see the first "bad" value. + * o Especially compared to the former Tk_ConfigureWidget calls, + * the new interface is overly complex. If there was a big + * performance win, it might be worth the effort. But let's + * face it, this biggest wins are in processing custom options + * values with thousands of elements. Most common resources + * (font, color, etc) have string tokens anyways. + * + * On the other hand, the replacement functions in this file fell + * into place quite easily both from the aspect of API writer and + * user. The biggest benefit is that you don't need to change lots + * of working code just to get the benefits of Tcl_Objs. + * + */ + +#include "bltInt.h" + +#include +#include "bltFont.h" +#include "bltPicture.h" +#include "bltBgStyle.h" + +#if (_TK_VERSION < _VERSION(8,1,0)) +/* + *--------------------------------------------------------------------------- + * + * Tk_GetAnchorFromObj -- + * + * Return a Tk_Anchor value based on the value of the objPtr. + * + * Results: + * The return value is a standard TCL result. If an error occurs during + * conversion, an error message is left in the interpreter's result + * unless "interp" is NULL. + * + * Side effects: + * The object gets converted by Tcl_GetIndexFromObj. + * + *--------------------------------------------------------------------------- + */ +int +Tk_GetAnchorFromObj( + Tcl_Interp *interp, /* Used for error reporting. */ + Tcl_Obj *objPtr, /* The object we are trying to get the + * value from. */ + Tk_Anchor *anchorPtr) /* Where to place the Tk_Anchor that + * corresponds to the string value of + * objPtr. */ +{ + return Tk_GetAnchor(interp, Tcl_GetString(objPtr), anchorPtr); +} + +/* + *--------------------------------------------------------------------------- + * + * Tk_GetJustifyFromObj -- + * + * Return a Tk_Justify value based on the value of the objPtr. + * + * Results: + * The return value is a standard TCL result. If an error occurs during + * conversion, an error message is left in the interpreter's result + * unless "interp" is NULL. + * + * Side effects: + * The object gets converted by Tcl_GetIndexFromObj. + * + *--------------------------------------------------------------------------- + */ +int +Tk_GetJustifyFromObj( + Tcl_Interp *interp, /* Used for error reporting. */ + Tcl_Obj *objPtr, /* The object we are trying to get the + * value from. */ + Tk_Justify *justifyPtr) /* Where to place the Tk_Justify that + * corresponds to the string value of + * objPtr. */ +{ + return Tk_GetJustify(interp, Tcl_GetString(objPtr), justifyPtr); +} +/* + *--------------------------------------------------------------------------- + * + * Tk_GetReliefFromObj -- + * + * Return an integer value based on the value of the objPtr. + * + * Results: + * The return value is a standard TCL result. If an error occurs during + * conversion, an error message is left in the interpreter's result + * unless "interp" is NULL. + * + * Side effects: + * The object gets converted by Tcl_GetIndexFromObj. + * + *--------------------------------------------------------------------------- + */ +int +Tk_GetReliefFromObj( + Tcl_Interp *interp, /* Used for error reporting. */ + Tcl_Obj *objPtr, /* The object we are trying to get the + * value from. */ + int *reliefPtr) /* Where to place the answer. */ +{ + return Tk_GetRelief(interp, Tcl_GetString(objPtr), reliefPtr); +} + + +/* + *--------------------------------------------------------------------------- + * + * Tk_Alloc3DBorderFromObj -- + * + * Given a Tcl_Obj *, map the value to a corresponding + * Tk_3DBorder structure based on the tkwin given. + * + * Results: + * The return value is a token for a data structure describing a + * 3-D border. This token may be passed to procedures such as + * Blt_Draw3DRectangle and Tk_Free3DBorder. If an error prevented + * the border from being created then NULL is returned and an error + * message will be left in the interp's result. + * + * Side effects: + * The border is added to an internal database with a reference + * count. For each call to this procedure, there should eventually + * be a call to FreeBorderObjProc so that the database is + * cleaned up when borders aren't in use anymore. + * + *--------------------------------------------------------------------------- + */ +Tk_3DBorder +Tk_Alloc3DBorderFromObj( + Tcl_Interp *interp, /* Interp for error results. */ + Tk_Window tkwin, /* Need the screen the border is used on.*/ + Tcl_Obj *objPtr) /* Object giving name of color for window + * background. */ +{ + return Tk_Get3DBorder(interp, tkwin, Tcl_GetString(objPtr)); +} +/* + *--------------------------------------------------------------------------- + * + * Tk_AllocBitmapFromObj -- + * + * Given a Tcl_Obj *, map the value to a corresponding + * Pixmap structure based on the tkwin given. + * + * Results: + * The return value is the X identifer for the desired bitmap + * (i.e. a Pixmap with a single plane), unless string couldn't be + * parsed correctly. In this case, None is returned and an error + * message is left in the interp's result. The caller should never + * modify the bitmap that is returned, and should eventually call + * Tk_FreeBitmapFromObj when the bitmap is no longer needed. + * + * Side effects: + * The bitmap is added to an internal database with a reference count. + * For each call to this procedure, there should eventually be a call + * to Tk_FreeBitmapFromObj, so that the database can be cleaned up + * when bitmaps aren't needed anymore. + * + *--------------------------------------------------------------------------- + */ +Pixmap +Tk_AllocBitmapFromObj( + Tcl_Interp *interp, /* Interp for error results. This may + * be NULL. */ + Tk_Window tkwin, /* Need the screen the bitmap is used on.*/ + Tcl_Obj *objPtr) /* Object describing bitmap; see manual + * entry for legal syntax of string value. */ +{ + return Tk_GetBitmap(interp, tkwin, Tcl_GetString(objPtr)); +} + +/* + *--------------------------------------------------------------------------- + * + * Tk_AllocFontFromObj -- + * + * Given a string description of a font, map the description to a + * corresponding Blt_Font that represents the font. + * + * Results: + * The return value is token for the font, or NULL if an error + * prevented the font from being created. If NULL is returned, an + * error message will be left in interp's result object. + * + * Side effects: + * The font is added to an internal database with a reference + * count. For each call to this procedure, there should eventually + * be a call to Blt_FreeFont() or Blt_FreeFontFromObj() so that the + * database is cleaned up when fonts aren't in use anymore. + * + *--------------------------------------------------------------------------- + */ +Tk_Font +Tk_AllocFontFromObj( + Tcl_Interp *interp, /* Interp for database and error return. */ + Tk_Window tkwin, /* For screen on which font will be used. */ + Tcl_Obj *objPtr) /* Object describing font, as: named font, + * native format, or parseable string. */ +{ + return Tk_GetFont(interp, tkwin, Tcl_GetString(objPtr)); +} + +/* + *--------------------------------------------------------------------------- + * + * Tk_AllocCursorFromObj -- + * + * Given a Tcl_Obj *, map the value to a corresponding + * Tk_Cursor structure based on the tkwin given. + * + * Results: + * The return value is the X identifer for the desired cursor, + * unless objPtr couldn't be parsed correctly. In this case, + * None is returned and an error message is left in the interp's result. + * The caller should never modify the cursor that is returned, and + * should eventually call Tk_FreeCursorFromObj when the cursor is no + * longer needed. + * + * Side effects: + * The cursor is added to an internal database with a reference count. + * For each call to this procedure, there should eventually be a call + * to Tk_FreeCursorFromObj, so that the database can be cleaned up + * when cursors aren't needed anymore. + * + *--------------------------------------------------------------------------- + */ +Tk_Cursor +Tk_AllocCursorFromObj( + Tcl_Interp *interp, /* Interp for error results. */ + Tk_Window tkwin, /* Window in which the cursor will be used.*/ + Tcl_Obj *objPtr) /* Object describing cursor; see manual + * entry for description of legal + * syntax of this obj's string rep. */ +{ + return Tk_GetCursor(interp, tkwin, Tcl_GetString(objPtr)); +} + +/* + *--------------------------------------------------------------------------- + * + * Tk_AllocColorFromObj -- + * + * Given a Tcl_Obj *, map the value to a corresponding + * XColor structure based on the tkwin given. + * + * Results: + * The return value is a pointer to an XColor structure that + * indicates the red, blue, and green intensities for the color + * given by the string in objPtr, and also specifies a pixel value + * to use to draw in that color. If an error occurs, NULL is + * returned and an error message will be left in interp's result + * (unless interp is NULL). + * + * Side effects: + * The color is added to an internal database with a reference count. + * For each call to this procedure, there should eventually be a call + * to Tk_FreeColorFromObj so that the database is cleaned up when colors + * aren't in use anymore. + * + *--------------------------------------------------------------------------- + */ +XColor * +Tk_AllocColorFromObj( + Tcl_Interp *interp, /* Used only for error reporting. If NULL, + * then no messages are provided. */ + Tk_Window tkwin, /* Window in which the color will be used.*/ + Tcl_Obj *objPtr) /* Object that describes the color; string + * value is a color name such as "red" or + * "#ff0000".*/ +{ + const char *string; + + string = Tcl_GetString(objPtr); + return Tk_GetColor(interp, tkwin, Tk_GetUid(string)); +} +#endif + + +/* + *--------------------------------------------------------------------------- + * + * Blt_GetPositionFromObj -- + * + * Convert a string representing a numeric position. + * A position can be in one of the following forms. + * + * number - number of the item in the hierarchy, indexed + * from zero. + * "end" - last position in the hierarchy. + * + * Results: + * A standard TCL result. If "string" is a valid index, then + * *indexPtr is filled with the corresponding numeric index. + * If "end" was selected then *indexPtr is set to -1. + * Otherwise an error message is left in interp->result. + * + * Side effects: + * None. + * + *--------------------------------------------------------------------------- + */ +int +Blt_GetPositionFromObj( + Tcl_Interp *interp, /* Interpreter to report results back + * to. */ + Tcl_Obj *objPtr, /* Tcl_Obj representation of the index. + * Can be an integer or "end" to refer + * to the last index. */ + long *indexPtr) /* Holds the converted index. */ +{ + const char *string; + + string = Tcl_GetString(objPtr); + if ((string[0] == 'e') && (strcmp(string, "end") == 0)) { + *indexPtr = -1; /* Indicates last position in hierarchy. */ + } else { + long position; + + if (Tcl_GetLongFromObj(interp, objPtr, &position) != TCL_OK) { + return TCL_ERROR; + } + if (position < 0) { + Tcl_AppendResult(interp, "bad position \"", string, "\"", + (char *)NULL); + return TCL_ERROR; + } + *indexPtr = position; + } + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_GetPixelsFromObj -- + * + * Like Tk_GetPixelsFromObj, but checks for negative, zero. + * + * Results: + * A standard TCL result. + * + *--------------------------------------------------------------------------- + */ +int +Blt_GetPixelsFromObj( + Tcl_Interp *interp, + Tk_Window tkwin, + Tcl_Obj *objPtr, + int check, /* Can be PIXELS_POS, PIXELS_NNEG, + * or PIXELS_ANY, */ + int *valuePtr) +{ + int length; + + if (Tk_GetPixelsFromObj(interp, tkwin, objPtr, &length) != TCL_OK) { + return TCL_ERROR; + } + if (length >= SHRT_MAX) { + Tcl_AppendResult(interp, "bad distance \"", Tcl_GetString(objPtr), + "\": too big to represent", (char *)NULL); + return TCL_ERROR; + } + switch (check) { + case PIXELS_NNEG: + if (length < 0) { + Tcl_AppendResult(interp, "bad distance \"", Tcl_GetString(objPtr), + "\": can't be negative", (char *)NULL); + return TCL_ERROR; + } + break; + + case PIXELS_POS: + if (length <= 0) { + Tcl_AppendResult(interp, "bad distance \"", Tcl_GetString(objPtr), + "\": must be positive", (char *)NULL); + return TCL_ERROR; + } + break; + + case PIXELS_ANY: + break; + } + *valuePtr = length; + return TCL_OK; +} + +int +Blt_GetPadFromObj( + Tcl_Interp *interp, /* Interpreter to send results back to */ + Tk_Window tkwin, /* Window */ + Tcl_Obj *objPtr, /* Pixel value string */ + Blt_Pad *padPtr) +{ + int side1, side2; + int objc; + Tcl_Obj **objv; + + if (Tcl_ListObjGetElements(interp, objPtr, &objc, &objv) != TCL_OK) { + return TCL_ERROR; + } + if ((objc < 1) || (objc > 2)) { + Tcl_AppendResult(interp, "wrong # elements in padding list", + (char *)NULL); + return TCL_ERROR; + } + if (Blt_GetPixelsFromObj(interp, tkwin, objv[0], PIXELS_NNEG, + &side1) != TCL_OK) { + return TCL_ERROR; + } + side2 = side1; + if ((objc > 1) && + (Blt_GetPixelsFromObj(interp, tkwin, objv[1], PIXELS_NNEG, + &side2) != TCL_OK)) { + return TCL_ERROR; + } + /* Don't update the pad structure until we know both values are okay. */ + padPtr->side1 = side1; + padPtr->side2 = side2; + return TCL_OK; +} + +int +Blt_GetStateFromObj( + Tcl_Interp *interp, /* Interpreter to send results back to */ + Tcl_Obj *objPtr, /* Pixel value string */ + int *statePtr) +{ + char c; + const char *string; + int length; + + string = Tcl_GetStringFromObj(objPtr, &length); + c = string[0]; + if ((c == 'n') && (strncmp(string, "normal", length) == 0)) { + *statePtr = STATE_NORMAL; + } else if ((c == 'd') && (strncmp(string, "disabled", length) == 0)) { + *statePtr = STATE_DISABLED; + } else if ((c == 'a') && (strncmp(string, "active", length) == 0)) { + *statePtr = STATE_ACTIVE; + } else { + Tcl_AppendResult(interp, "bad state \"", string, + "\": should be normal, active, or disabled", (char *)NULL); + return TCL_ERROR; + } + return TCL_OK; +} + +const char * +Blt_NameOfState(int state) +{ + switch (state) { + case STATE_ACTIVE: + return "active"; + case STATE_DISABLED: + return "disabled"; + case STATE_NORMAL: + return "normal"; + default: + return "???"; + } +} + + +/* + *--------------------------------------------------------------------------- + * + * Blt_NameOfFill -- + * + * Converts the integer representing the fill style into a string. + * + *--------------------------------------------------------------------------- + */ +const char * +Blt_NameOfFill(int fill) +{ + switch (fill) { + case FILL_X: + return "x"; + case FILL_Y: + return "y"; + case FILL_NONE: + return "none"; + case FILL_BOTH: + return "both"; + default: + return "unknown value"; + } +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_GetFillFromObj -- + * + * Converts the fill style string into its numeric representation. + * + * Valid style strings are: + * + * "none" Use neither plane. + * "x" X-coordinate plane. + * "y" Y-coordinate plane. + * "both" Use both coordinate planes. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +int +Blt_GetFillFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, int *fillPtr) +{ + char c; + const char *string; + int length; + + string = Tcl_GetStringFromObj(objPtr, &length); + c = string[0]; + if ((c == 'n') && (strncmp(string, "none", length) == 0)) { + *fillPtr = FILL_NONE; + } else if ((c == 'x') && (strncmp(string, "x", length) == 0)) { + *fillPtr = FILL_X; + } else if ((c == 'y') && (strncmp(string, "y", length) == 0)) { + *fillPtr = FILL_Y; + } else if ((c == 'b') && (strncmp(string, "both", length) == 0)) { + *fillPtr = FILL_BOTH; + } else { + Tcl_AppendResult(interp, "bad argument \"", string, + "\": should be \"none\", \"x\", \"y\", or \"both\"", (char *)NULL); + return TCL_ERROR; + } + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_NameOfResize -- + * + * Converts the resize value into its string representation. + * + * Results: + * Returns a pointer to the static name string. + * + *--------------------------------------------------------------------------- + */ +const char * +Blt_NameOfResize(int resize) +{ + switch (resize & RESIZE_BOTH) { + case RESIZE_NONE: + return "none"; + case RESIZE_EXPAND: + return "expand"; + case RESIZE_SHRINK: + return "shrink"; + case RESIZE_BOTH: + return "both"; + default: + return "unknown resize value"; + } +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_GetResizeFromObj -- + * + * Converts the resize string into its numeric representation. + * + * Valid style strings are: + * + * "none" + * "expand" + * "shrink" + * "both" + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +int +Blt_GetResizeFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, int *resizePtr) +{ + char c; + const char *string; + int length; + + string = Tcl_GetStringFromObj(objPtr, &length); + c = string[0]; + if ((c == 'n') && (strncmp(string, "none", length) == 0)) { + *resizePtr = RESIZE_NONE; + } else if ((c == 'b') && (strncmp(string, "both", length) == 0)) { + *resizePtr = RESIZE_BOTH; + } else if ((c == 'e') && (strncmp(string, "expand", length) == 0)) { + *resizePtr = RESIZE_EXPAND; + } else if ((c == 's') && (strncmp(string, "shrink", length) == 0)) { + *resizePtr = RESIZE_SHRINK; + } else { + Tcl_AppendResult(interp, "bad resize argument \"", string, + "\": should be \"none\", \"expand\", \"shrink\", or \"both\"", + (char *)NULL); + return TCL_ERROR; + } + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_GetDashesFromObj -- + * + * Converts a TCL list of dash values into a dash list ready for + * use with XSetDashes. + * + * A valid list dash values can have zero through 11 elements + * (PostScript limit). Values must be between 1 and 255. Although + * a list of 0 (like the empty string) means no dashes. + * + * Results: + * A standard TCL result. If the list represented a valid dash + * list TCL_OK is returned and *dashesPtr* will contain the + * valid dash list. Otherwise, TCL_ERROR is returned and + * interp->result will contain an error message. + * + * + *--------------------------------------------------------------------------- + */ +int +Blt_GetDashesFromObj( + Tcl_Interp *interp, + Tcl_Obj *objPtr, + Blt_Dashes *dashesPtr) +{ + const char *string; + char c; + + string = Tcl_GetString(objPtr); + if (string == NULL) { + dashesPtr->values[0] = 0; + return TCL_OK; + } + c = string[0]; + if (c == '\0') { + dashesPtr->values[0] = 0; + } else if ((c == 'd') && (strcmp(string, "dot") == 0)) { + /* 1 */ + dashesPtr->values[0] = 1; + dashesPtr->values[1] = 0; + } else if ((c == 'd') && (strcmp(string, "dash") == 0)) { + /* 5 2 */ + dashesPtr->values[0] = 5; + dashesPtr->values[1] = 2; + dashesPtr->values[2] = 0; + } else if ((c == 'd') && (strcmp(string, "dashdot") == 0)) { + /* 2 4 2 */ + dashesPtr->values[0] = 2; + dashesPtr->values[1] = 4; + dashesPtr->values[2] = 2; + dashesPtr->values[3] = 0; + } else if ((c == 'd') && (strcmp(string, "dashdotdot") == 0)) { + /* 2 4 2 2 */ + dashesPtr->values[0] = 2; + dashesPtr->values[1] = 4; + dashesPtr->values[2] = 2; + dashesPtr->values[3] = 2; + dashesPtr->values[4] = 0; + } else { + int objc; + Tcl_Obj **objv; + int i; + + if (Tcl_ListObjGetElements(interp, objPtr, &objc, &objv) != TCL_OK) { + return TCL_ERROR; + } + if (objc > 11) { /* This is the postscript limit */ + Tcl_AppendResult(interp, "too many values in dash list \"", + string, "\"", (char *)NULL); + return TCL_ERROR; + } + for (i = 0; i < objc; i++) { + int value; + + if (Tcl_GetIntFromObj(interp, objv[i], &value) != TCL_OK) { + return TCL_ERROR; + } + /* + * Backward compatibility: + * Allow list of 0 to turn off dashes + */ + if ((value == 0) && (objc == 1)) { + break; + } + if ((value < 1) || (value > 255)) { + Tcl_AppendResult(interp, "dash value \"", + Tcl_GetString(objv[i]), "\" is out of range", + (char *)NULL); + return TCL_ERROR; + } + dashesPtr->values[i] = (unsigned char)value; + } + /* Make sure the array ends with a NUL byte */ + dashesPtr->values[i] = 0; + } + return TCL_OK; +} + +const char * +Blt_NameOfSide(int side) +{ + switch (side) { + case SIDE_LEFT: + return "left"; + case SIDE_RIGHT: + return "right"; + case SIDE_BOTTOM: + return "bottom"; + case SIDE_TOP: + return "top"; + } + return "unknown side value"; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_GetSideFromObj -- + * + * Converts the fill style string into its numeric representation. + * + * Valid style strings are "left", "right", "top", or "bottom". + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED */ +int +Blt_GetSideFromObj( + Tcl_Interp *interp, /* Interpreter to send results back to */ + Tcl_Obj *objPtr, /* Value string */ + int *sidePtr) /* (out) Token representing side: + * either SIDE_LEFT, SIDE_RIGHT, + * SIDE_TOP, or SIDE_BOTTOM. */ +{ + char c; + const char *string; + int length; + + string = Tcl_GetStringFromObj(objPtr, &length); + c = string[0]; + if ((c == 'l') && (strncmp(string, "left", length) == 0)) { + *sidePtr = SIDE_LEFT; + } else if ((c == 'r') && (strncmp(string, "right", length) == 0)) { + *sidePtr = SIDE_RIGHT; + } else if ((c == 't') && (strncmp(string, "top", length) == 0)) { + *sidePtr = SIDE_TOP; + } else if ((c == 'b') && (strncmp(string, "bottom", length) == 0)) { + *sidePtr = SIDE_BOTTOM; + } else { + Tcl_AppendResult(interp, "bad side \"", string, + "\": should be left, right, top, or bottom", (char *)NULL); + return TCL_ERROR; + } + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_ResetLimits -- + * + * Resets the limits to their default values. + * + * Results: + * None. + * + *--------------------------------------------------------------------------- + */ +void +Blt_ResetLimits(Blt_Limits *limitsPtr) /* Limits to be imposed on the value */ +{ + limitsPtr->flags = 0; + limitsPtr->min = LIMITS_MIN; + limitsPtr->max = LIMITS_MAX; + limitsPtr->nom = LIMITS_NOM; +} + +int +Blt_GetLimitsFromObj(Tcl_Interp *interp, Tk_Window tkwin, Tcl_Obj *objPtr, + Blt_Limits *limitsPtr) +{ + int values[3]; + int nValues; + int limitsFlags; + + /* Initialize limits to default values */ + values[2] = LIMITS_NOM; + values[1] = LIMITS_MAX; + values[0] = LIMITS_MIN; + limitsFlags = 0; + nValues = 0; + if (objPtr != NULL) { + Tcl_Obj **objv; + int objc; + int i; + + if (Tcl_ListObjGetElements(interp, objPtr, &objc, &objv) != TCL_OK) { + return TCL_ERROR; + } + if (objc > 3) { + Tcl_AppendResult(interp, "wrong # limits \"", Tcl_GetString(objPtr), + "\"", (char *)NULL); + return TCL_ERROR; + } + for (i = 0; i < objc; i++) { + const char *string; + int size; + + string = Tcl_GetString(objv[i]); + if (string[0] == '\0') { + continue; /* Empty string: use default value */ + } + limitsFlags |= (1 << i); + if (Tk_GetPixelsFromObj(interp, tkwin, objv[i], &size) != TCL_OK) { + return TCL_ERROR; + } + if ((size < LIMITS_MIN) || (size > LIMITS_MAX)) { + Tcl_AppendResult(interp, "bad limit \"", string, "\"", + (char *)NULL); + return TCL_ERROR; + } + values[i] = size; + } + nValues = objc; + } + /* + * Check the limits specified. We can't check the requested size of + * widgets. + */ + switch (nValues) { + case 1: + limitsFlags |= (LIMITS_MIN_SET | LIMITS_MAX_SET); + values[1] = values[0]; /* Set minimum and maximum to value */ + break; + + case 2: + if (values[1] < values[0]) { + Tcl_AppendResult(interp, "bad range \"", Tcl_GetString(objPtr), + "\": min > max", (char *)NULL); + return TCL_ERROR; /* Minimum is greater than maximum */ + } + break; + + case 3: + if (values[1] < values[0]) { + Tcl_AppendResult(interp, "bad range \"", Tcl_GetString(objPtr), + "\": min > max", (char *)NULL); + return TCL_ERROR; /* Minimum is greater than maximum */ + } + if ((values[2] < values[0]) || (values[2] > values[1])) { + Tcl_AppendResult(interp, "nominal value \"", Tcl_GetString(objPtr), + "\" out of range", (char *)NULL); + return TCL_ERROR; /* Nominal is outside of range defined + * by minimum and maximum */ + } + break; + } + limitsPtr->min = values[0]; + limitsPtr->max = values[1]; + limitsPtr->nom = values[2]; + limitsPtr->flags = limitsFlags; + return TCL_OK; +} + +/* Configuration option helper routines */ + +/* + *--------------------------------------------------------------------------- + * + * DoConfig -- + * + * This procedure applies a single configuration option + * to a widget record. + * + * Results: + * A standard TCL return value. + * + * Side effects: + * WidgRec is modified as indicated by specPtr and value. + * The old value is recycled, if that is appropriate for + * the value type. + * + *--------------------------------------------------------------------------- + */ +static int +DoConfig( + Tcl_Interp *interp, /* Interpreter for error reporting. */ + Tk_Window tkwin, /* Window containing widget (needed to + * set up X resources). */ + Blt_ConfigSpec *sp, /* Specifier to apply. */ + Tcl_Obj *objPtr, /* Value to use to fill in widgRec. */ + char *widgRec) /* Record whose fields are to be + * modified. Values must be properly + * initialized. */ +{ + char *ptr; + int objIsEmpty; + + objIsEmpty = FALSE; + if (objPtr == NULL) { + objIsEmpty = TRUE; + } else if (sp->specFlags & BLT_CONFIG_NULL_OK) { + int length; + + if (objPtr->bytes != NULL) { + length = objPtr->length; + } else { + Tcl_GetStringFromObj(objPtr, &length); + } + objIsEmpty = (length == 0); + } + do { + ptr = widgRec + sp->offset; + switch (sp->type) { + case BLT_CONFIG_ANCHOR: + { + Tk_Anchor anchor; + + if (Tk_GetAnchorFromObj(interp, objPtr, &anchor) != TCL_OK) { + return TCL_ERROR; + } + *(Tk_Anchor *)ptr = anchor; + } + break; + + case BLT_CONFIG_BITMAP: + { + Pixmap bitmap; + + if (objIsEmpty) { + bitmap = None; + } else { + bitmap = Tk_AllocBitmapFromObj(interp, tkwin, objPtr); + if (bitmap == None) { + return TCL_ERROR; + } + } + if (*(Pixmap *)ptr != None) { + Tk_FreeBitmap(Tk_Display(tkwin), *(Pixmap *)ptr); + } + *(Pixmap *)ptr = bitmap; + } + break; + + case BLT_CONFIG_BOOLEAN: + { + int bool; + + if (Tcl_GetBooleanFromObj(interp, objPtr, &bool) != TCL_OK) { + return TCL_ERROR; + } + *(int *)ptr = bool; + } + break; + + case BLT_CONFIG_BORDER: + { + Tk_3DBorder border; + + if (objIsEmpty) { + border = NULL; + } else { + border = Tk_Alloc3DBorderFromObj(interp, tkwin, objPtr); + if (border == NULL) { + return TCL_ERROR; + } + } + if (*(Tk_3DBorder *)ptr != NULL) { + Tk_Free3DBorder(*(Tk_3DBorder *)ptr); + } + *(Tk_3DBorder *)ptr = border; + } + break; + + case BLT_CONFIG_CAP_STYLE: + { + int cap; + Tk_Uid uid; + + uid = Tk_GetUid(Tcl_GetString(objPtr)); + if (Tk_GetCapStyle(interp, uid, &cap) != TCL_OK) { + return TCL_ERROR; + } + *(int *)ptr = cap; + } + break; + + case BLT_CONFIG_COLOR: + { + XColor *color; + + if (objIsEmpty) { + color = NULL; + } else { + color = Tk_GetColor(interp, tkwin, + Tk_GetUid(Tcl_GetString(objPtr))); + if (color == NULL) { + return TCL_ERROR; + } + } + if (*(XColor **)ptr != NULL) { + Tk_FreeColor(*(XColor **)ptr); + } + *(XColor **)ptr = color; + } + break; + + case BLT_CONFIG_CURSOR: + case BLT_CONFIG_ACTIVE_CURSOR: + { + Tk_Cursor cursor; + + if (objIsEmpty) { + cursor = None; + } else { + cursor = Tk_AllocCursorFromObj(interp, tkwin, objPtr); + if (cursor == None) { + return TCL_ERROR; + } + } + if (*(Tk_Cursor *)ptr != None) { + Tk_FreeCursor(Tk_Display(tkwin), *(Tk_Cursor *)ptr); + } + *(Tk_Cursor *)ptr = cursor; + if (sp->type == BLT_CONFIG_ACTIVE_CURSOR) { + Tk_DefineCursor(tkwin, cursor); + } + } + break; + + case BLT_CONFIG_CUSTOM: + if ((*sp->customPtr->parseProc)(sp->customPtr->clientData, interp, + tkwin, objPtr, widgRec, sp->offset, sp->specFlags) != TCL_OK) { + return TCL_ERROR; + } + break; + + case BLT_CONFIG_DOUBLE: + { + double value; + + if (Tcl_GetDoubleFromObj(interp, objPtr, &value) != TCL_OK) { + return TCL_ERROR; + } + *(double *)ptr = value; + } + break; + + case BLT_CONFIG_FONT: + { + Blt_Font font; + + if (objIsEmpty) { + font = NULL; + } else { + font = Blt_AllocFontFromObj(interp, tkwin, objPtr); + if (font == NULL) { + return TCL_ERROR; + } + } + if (*(Blt_Font *)ptr != NULL) { + Blt_FreeFont(*(Blt_Font *)ptr); + } + *(Blt_Font *)ptr = font; + } + break; + + case BLT_CONFIG_TK_FONT: + { + Tk_Font font; + + if (objIsEmpty) { + font = NULL; + } else { + font = Tk_AllocFontFromObj(interp, tkwin, objPtr); + if (font == NULL) { + return TCL_ERROR; + } + } + if (*(Tk_Font *)ptr != NULL) { + Tk_FreeFont(*(Tk_Font *)ptr); + } + *(Tk_Font *)ptr = font; + } + break; + + case BLT_CONFIG_INT: + { + int value; + + if (Tcl_GetIntFromObj(interp, objPtr, &value) != TCL_OK) { + return TCL_ERROR; + } + *(int *)ptr = value; + } + break; + + case BLT_CONFIG_JOIN_STYLE: + { + int join; + Tk_Uid uid; + + uid = Tk_GetUid(Tcl_GetString(objPtr)); + if (Tk_GetJoinStyle(interp, uid, &join) != TCL_OK) { + return TCL_ERROR; + } + *(int *)ptr = join; + } + break; + + case BLT_CONFIG_JUSTIFY: + { + Tk_Justify justify; + + if (Tk_GetJustifyFromObj(interp, objPtr, &justify) != TCL_OK) { + return TCL_ERROR; + } + *(Tk_Justify *)ptr = justify; + } + break; + + case BLT_CONFIG_MM: + { + double value; + + if (Tk_GetMMFromObj(interp, tkwin, objPtr, &value) != TCL_OK) { + return TCL_ERROR; + } + *(double *)ptr = value; + } + break; + + + case BLT_CONFIG_RELIEF: + { + int relief; + + if (Tk_GetReliefFromObj(interp, objPtr, &relief) != TCL_OK) { + return TCL_ERROR; + } + *(int *)ptr = relief; + } + break; + + case BLT_CONFIG_STRING: + { + char *value; + + value = (objIsEmpty) ? NULL : + Blt_AssertStrdup(Tcl_GetString(objPtr)); + if (*(char **)ptr != NULL) { + Blt_Free(*(char **)ptr); + } + *(char **)ptr = value; + } + break; + + case BLT_CONFIG_UID: + if (*(Blt_Uid *)ptr != NULL) { + Blt_FreeUid(*(Blt_Uid *)ptr); + } + if (objIsEmpty) { + *(Blt_Uid *)ptr = NULL; + } else { + *(Blt_Uid *)ptr = Blt_GetUid(Tcl_GetString(objPtr)); + } + break; + + case BLT_CONFIG_WINDOW: + { + Tk_Window tkwin2; + + if (objIsEmpty) { + tkwin2 = None; + } else { + const char *path; + + path = Tcl_GetString(objPtr); + tkwin2 = Tk_NameToWindow(interp, path, tkwin); + if (tkwin2 == NULL) { + return TCL_ERROR; + } + } + *(Tk_Window *)ptr = tkwin2; + } + break; + + case BLT_CONFIG_BITMASK: + { + int bool; + unsigned long mask, flags; + + if (Tcl_GetBooleanFromObj(interp, objPtr, &bool) != TCL_OK) { + return TCL_ERROR; + } + mask = (unsigned long)sp->customPtr; + flags = *(int *)ptr; + flags &= ~mask; + if (bool) { + flags |= mask; + } + *(int *)ptr = flags; + } + break; + + case BLT_CONFIG_BITMASK_INVERT: + { + int bool; + unsigned long mask, flags; + + if (Tcl_GetBooleanFromObj(interp, objPtr, &bool) != TCL_OK) { + return TCL_ERROR; + } + mask = (unsigned long)sp->customPtr; + flags = *(int *)ptr; + flags &= ~mask; + if (!bool) { + flags |= mask; + } + *(int *)ptr = flags; + } + break; + + case BLT_CONFIG_DASHES: + if (Blt_GetDashesFromObj(interp, objPtr, (Blt_Dashes *)ptr) + != TCL_OK) { + return TCL_ERROR; + } + break; + + + case BLT_CONFIG_FILL: + if (Blt_GetFillFromObj(interp, objPtr, (int *)ptr) != TCL_OK) { + return TCL_ERROR; + } + break; + + case BLT_CONFIG_RESIZE: + if (Blt_GetResizeFromObj(interp, objPtr, (int *)ptr) != TCL_OK) { + return TCL_ERROR; + } + break; + + case BLT_CONFIG_FLOAT: + { + double value; + + if (Tcl_GetDoubleFromObj(interp, objPtr, &value) != TCL_OK) { + return TCL_ERROR; + } + *(float *)ptr = (float)value; + } + break; + + case BLT_CONFIG_INT_NNEG: + { + long value; + + if (Blt_GetCountFromObj(interp, objPtr, COUNT_NNEG, + &value) != TCL_OK) { + return TCL_ERROR; + } + *(int *)ptr = (int)value; + } + break; + + + case BLT_CONFIG_INT_POS: + { + long value; + + if (Blt_GetCountFromObj(interp, objPtr, COUNT_POS, &value) + != TCL_OK) { + return TCL_ERROR; + } + *(int *)ptr = (int)value; + } + break; + + + case BLT_CONFIG_LIST: + { + const char **argv; + int argc; + + if (Tcl_SplitList(interp, Tcl_GetString(objPtr), &argc, &argv) + != TCL_OK) { + return TCL_ERROR; + } + if (*(char ***)ptr != NULL) { + Blt_Free(*(char ***)ptr); + } + *(const char ***)ptr = argv; + } + break; + + case BLT_CONFIG_LONG: + { + long value; + + if (Tcl_GetLongFromObj(interp, objPtr, &value) != TCL_OK) { + return TCL_ERROR; + } + *(long *)ptr = value; + } + break; + + case BLT_CONFIG_LONG_NNEG: + { + long value; + + if (Blt_GetCountFromObj(interp, objPtr, COUNT_NNEG, + &value) != TCL_OK) { + return TCL_ERROR; + } + *(long *)ptr = value; + } + break; + + + case BLT_CONFIG_LONG_POS: + { + long value; + + if (Blt_GetCountFromObj(interp, objPtr, COUNT_POS, &value) + != TCL_OK) { + return TCL_ERROR; + } + *(long *)ptr = value; + } + break; + + case BLT_CONFIG_OBJ: + { + Tcl_IncrRefCount(objPtr); + if (*(Tcl_Obj **)ptr != NULL) { + Tcl_DecrRefCount(*(Tcl_Obj **)ptr); + } + *(Tcl_Obj **)ptr = objPtr; + } + break; + + case BLT_CONFIG_PAD: + if (Blt_GetPadFromObj(interp, tkwin, objPtr, (Blt_Pad *)ptr) + != TCL_OK) { + return TCL_ERROR; + } + break; + + case BLT_CONFIG_PIXELS_NNEG: + { + int value; + + if (Blt_GetPixelsFromObj(interp, tkwin, objPtr, + PIXELS_NNEG, &value) != TCL_OK) { + return TCL_ERROR; + } + *(int *)ptr = value; + } + break; + + case BLT_CONFIG_PIXELS: + { + int value; + + if (Blt_GetPixelsFromObj(interp, tkwin, objPtr, PIXELS_ANY, + &value) != TCL_OK) { + return TCL_ERROR; + } + *(int *)ptr = value; + } + break; + + case BLT_CONFIG_PIXELS_POS: + { + int value; + + if (Blt_GetPixelsFromObj(interp, tkwin, objPtr, PIXELS_POS, + &value) != TCL_OK) { + return TCL_ERROR; + } + *(int *)ptr = value; + } + break; + + case BLT_CONFIG_STATE: + if (Blt_GetStateFromObj(interp, objPtr, (int *)ptr) != TCL_OK) { + return TCL_ERROR; + } + break; + + case BLT_CONFIG_SIDE: + if (Blt_GetSideFromObj(interp, objPtr, (int *)ptr) != TCL_OK) { + return TCL_ERROR; + } + break; + + case BLT_CONFIG_BACKGROUND: + { + Blt_Background style; + + if (objIsEmpty) { + style = NULL; + } else { + style = Blt_GetBackgroundFromObj(interp, tkwin, objPtr); + if (style == NULL) { + return TCL_ERROR; + } + } + if (*(Blt_Background *)ptr != NULL) { + Blt_FreeBackground(*(Blt_Background *)ptr); + } + *(Blt_Background *)ptr = style; + } + break; + + case BLT_CONFIG_PIX32: + if (Blt_GetPixelFromObj(interp, objPtr, (Blt_Pixel *)ptr)!=TCL_OK) { + return TCL_ERROR; + } + break; + + default: + Tcl_AppendResult(interp, "bad config table: unknown type ", + Blt_Itoa(sp->type), (char *)NULL); + return TCL_ERROR; + } + sp++; + } while ((sp->switchName == NULL) && (sp->type != BLT_CONFIG_END)); + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * FormatConfigValue -- + * + * This procedure formats the current value of a configuration + * option. + * + * Results: + * The return value is the formatted value of the option given + * by specPtr and widgRec. If the value is static, so that it + * need not be freed, *freeProcPtr will be set to NULL; otherwise + * *freeProcPtr will be set to the address of a procedure to + * free the result, and the caller must invoke this procedure + * when it is finished with the result. + * + * Side effects: + * None. + * + *--------------------------------------------------------------------------- + */ +static Tcl_Obj * +FormatConfigValue( + Tcl_Interp *interp, /* Interpreter for use in real conversions. */ + Tk_Window tkwin, /* Window corresponding to widget. */ + Blt_ConfigSpec *sp, /* Pointer to information describing option. + * Must not point to a synonym option. */ + char *widgRec) /* Pointer to record holding current + * values of info for widget. */ +{ + char *ptr; + const char *string; + + ptr = widgRec + sp->offset; + string = ""; + switch (sp->type) { + case BLT_CONFIG_ANCHOR: + string = Tk_NameOfAnchor(*(Tk_Anchor *)ptr); + break; + + case BLT_CONFIG_BITMAP: + if (*(Pixmap *)ptr != None) { + string = Tk_NameOfBitmap(Tk_Display(tkwin), *(Pixmap *)ptr); + } + break; + + case BLT_CONFIG_BOOLEAN: + return Tcl_NewBooleanObj(*(int *)ptr); + + case BLT_CONFIG_BORDER: + if (*(Tk_3DBorder *)ptr != NULL) { + string = Tk_NameOf3DBorder(*(Tk_3DBorder *)ptr); + } + break; + + case BLT_CONFIG_CAP_STYLE: + string = Tk_NameOfCapStyle(*(int *)ptr); + break; + + case BLT_CONFIG_COLOR: + if (*(XColor **)ptr != NULL) { + string = Tk_NameOfColor(*(XColor **)ptr); + } + break; + + case BLT_CONFIG_CURSOR: + case BLT_CONFIG_ACTIVE_CURSOR: + if (*(Tk_Cursor *)ptr != None) { + string = Tk_NameOfCursor(Tk_Display(tkwin), *(Tk_Cursor *)ptr); + } + break; + + case BLT_CONFIG_CUSTOM: + return (*sp->customPtr->printProc) + (sp->customPtr->clientData, interp, tkwin, widgRec, + sp->offset, sp->specFlags); + + case BLT_CONFIG_DOUBLE: + return Tcl_NewDoubleObj(*(double *)ptr); + + case BLT_CONFIG_FONT: + if (*(Blt_Font *)ptr != NULL) { + string = Blt_NameOfFont(*(Blt_Font *)ptr); + } + break; + + case BLT_CONFIG_TK_FONT: + if (*(Tk_Font *)ptr != NULL) { + string = Tk_NameOfFont(*(Tk_Font *)ptr); + } + break; + + case BLT_CONFIG_INT: + return Tcl_NewIntObj(*(int *)ptr); + + case BLT_CONFIG_JOIN_STYLE: + string = Tk_NameOfJoinStyle(*(int *)ptr); + break; + + case BLT_CONFIG_JUSTIFY: + string = Tk_NameOfJustify(*(Tk_Justify *)ptr); + break; + + case BLT_CONFIG_MM: + return Tcl_NewDoubleObj(*(double *)ptr); + + case BLT_CONFIG_PIXELS: + case BLT_CONFIG_PIXELS_POS: + case BLT_CONFIG_PIXELS_NNEG: + return Tcl_NewIntObj(*(int *)ptr); + + case BLT_CONFIG_RELIEF: + string = Tk_NameOfRelief(*(int *)ptr); + break; + + case BLT_CONFIG_STRING: + case BLT_CONFIG_UID: + if (*(char **)ptr != NULL) { + string = *(char **)ptr; + } + break; + + case BLT_CONFIG_BITMASK: + { + unsigned long flag; + + flag = (*(unsigned long *)ptr) & (unsigned long)sp->customPtr; + return Tcl_NewBooleanObj((flag != 0)); + } + + case BLT_CONFIG_BITMASK_INVERT: + { + unsigned long flag; + + flag = (*(unsigned long *)ptr) & (unsigned long)sp->customPtr; + return Tcl_NewBooleanObj((flag == 0)); + } + + case BLT_CONFIG_DASHES: + { + unsigned char *p; + Tcl_Obj *listObjPtr; + Blt_Dashes *dashesPtr = (Blt_Dashes *)ptr; + + listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL); + for(p = dashesPtr->values; *p != 0; p++) { + Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewIntObj(*p)); + } + return listObjPtr; + } + + case BLT_CONFIG_INT_NNEG: + case BLT_CONFIG_INT_POS: + return Tcl_NewIntObj(*(int *)ptr); + + case BLT_CONFIG_FILL: + string = Blt_NameOfFill(*(int *)ptr); + break; + + case BLT_CONFIG_RESIZE: + string = Blt_NameOfResize(*(int *)ptr); + break; + + case BLT_CONFIG_FLOAT: + { + double x = *(float *)ptr; + return Tcl_NewDoubleObj(x); + } + + case BLT_CONFIG_LIST: + { + Tcl_Obj *objPtr, *listObjPtr; + char *const *p; + + listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL); + for (p = *(char ***)ptr; *p != NULL; p++) { + objPtr = Tcl_NewStringObj(*p, -1); + Tcl_ListObjAppendElement(interp, listObjPtr, objPtr); + } + return listObjPtr; + } + + case BLT_CONFIG_LONG: + return Tcl_NewLongObj(*(long *)ptr); + + case BLT_CONFIG_LONG_NNEG: + case BLT_CONFIG_LONG_POS: + return Tcl_NewLongObj(*(long *)ptr); + + case BLT_CONFIG_OBJ: + if (*(Tcl_Obj **)ptr != NULL) { + return *(Tcl_Obj **)ptr; + } + break; + + case BLT_CONFIG_PAD: + { + Blt_Pad *padPtr = (Blt_Pad *)ptr; + Tcl_Obj *objPtr, *listObjPtr; + + listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL); + objPtr = Tcl_NewIntObj(padPtr->side1); + Tcl_ListObjAppendElement(interp, listObjPtr, objPtr); + objPtr = Tcl_NewIntObj(padPtr->side2); + Tcl_ListObjAppendElement(interp, listObjPtr, objPtr); + return listObjPtr; + } + + case BLT_CONFIG_STATE: + string = Blt_NameOfState(*(int *)ptr); + break; + + case BLT_CONFIG_SIDE: + string = Blt_NameOfSide(*(int *)ptr); + break; + + case BLT_CONFIG_BACKGROUND: + if (*(Blt_Background *)ptr != NULL) { + string = Blt_NameOfBackground(*(Blt_Background *)ptr); + } + break; + + case BLT_CONFIG_PIX32: + string = Blt_NameOfPixel((Blt_Pixel *)ptr); + break; + + default: + string = "?? unknown type ??"; + } + return Tcl_NewStringObj(string, -1); +} + +/* + *--------------------------------------------------------------------------- + * + * FormatConfigInfo -- + * + * Create a valid TCL list holding the configuration information + * for a single configuration option. + * + * Results: + * A TCL list, dynamically allocated. The caller is expected to + * arrange for this list to be freed eventually. + * + * Side effects: + * Memory is allocated. + * + *--------------------------------------------------------------------------- + */ +static Tcl_Obj * +FormatConfigInfo( + Tcl_Interp *interp, /* Interpreter to use for things + * like floating-point precision. */ + Tk_Window tkwin, /* Window corresponding to widget. */ + Blt_ConfigSpec *sp, /* Pointer to information describing + * option. */ + char *widgRec) /* Pointer to record holding current + * values of info for widget. */ +{ + Tcl_Obj *listObjPtr; + + listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL); + if (sp->switchName != NULL) { + Tcl_ListObjAppendElement(interp, listObjPtr, + Tcl_NewStringObj(sp->switchName, -1)); + } else { + Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewStringObj("", -1)); + } + if (sp->dbName != NULL) { + Tcl_ListObjAppendElement(interp, listObjPtr, + Tcl_NewStringObj(sp->dbName, -1)); + } else { + Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewStringObj("", -1)); + } + if (sp->type == BLT_CONFIG_SYNONYM) { + return listObjPtr; + } + if (sp->dbClass != NULL) { + Tcl_ListObjAppendElement(interp, listObjPtr, + Tcl_NewStringObj(sp->dbClass, -1)); + } else { + Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewStringObj("", -1)); + } + if (sp->defValue != NULL) { + Tcl_ListObjAppendElement(interp, listObjPtr, + Tcl_NewStringObj(sp->defValue, -1)); + } else { + Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewStringObj("", -1)); + } + Tcl_ListObjAppendElement(interp, listObjPtr, + FormatConfigValue(interp, tkwin, sp, widgRec)); + return listObjPtr; +} + +/* + *--------------------------------------------------------------------------- + * + * FindConfigSpec -- + * + * Search through a table of configuration specs, looking for + * one that matches a given switchName. + * + * Results: + * The return value is a pointer to the matching entry, or NULL + * if nothing matched. In that case an error message is left + * in the interp's result. + * + * Side effects: + * None. + * + *--------------------------------------------------------------------------- + */ +static Blt_ConfigSpec * +FindConfigSpec( + Tcl_Interp *interp, /* Used for reporting errors. */ + Blt_ConfigSpec *specs, /* Pointer to table of configuration + * specifications for a widget. */ + Tcl_Obj *objPtr, /* Name (suitable for use in a "config" + * command) identifying particular option. */ + int needFlags, /* Flags that must be present in matching + * entry. */ + int hateFlags) /* Flags that must NOT be present in + * matching entry. */ +{ + Blt_ConfigSpec *matchPtr; /* Matching spec, or NULL. */ + Blt_ConfigSpec *sp; + const char *string; + char c; /* First character of current argument. */ + int length; + + string = Tcl_GetStringFromObj(objPtr, &length); + c = string[1]; + matchPtr = NULL; + for (sp = specs; sp->type != BLT_CONFIG_END; sp++) { + if (sp->switchName == NULL) { + continue; + } + if ((sp->switchName[1] != c) || + (strncmp(sp->switchName, string, length) != 0)) { + continue; + } + if (((sp->specFlags & needFlags) != needFlags) || + (sp->specFlags & hateFlags)) { + continue; + } + if (sp->switchName[length] == 0) { + matchPtr = sp; + goto gotMatch; + } + if (matchPtr != NULL) { + if (interp != NULL) { + Tcl_AppendResult(interp, "ambiguous option \"", string, "\"", + (char *)NULL); + } + return (Blt_ConfigSpec *)NULL; + } + matchPtr = sp; + } + + if (matchPtr == NULL) { + if (interp != NULL) { + Tcl_AppendResult(interp, "unknown option \"", string, "\"", + (char *)NULL); + } + return (Blt_ConfigSpec *)NULL; + } + + /* + * Found a matching entry. If it's a synonym, then find the + * entry that it's a synonym for. + */ + + gotMatch: + sp = matchPtr; + if (sp->type == BLT_CONFIG_SYNONYM) { + for (sp = specs; /*empty*/; sp++) { + if (sp->type == BLT_CONFIG_END) { + if (interp != NULL) { + Tcl_AppendResult(interp, + "couldn't find synonym for option \"", string, "\"", + (char *)NULL); + } + return (Blt_ConfigSpec *) NULL; + } + if ((sp->dbName == matchPtr->dbName) && + (sp->type != BLT_CONFIG_SYNONYM) && + ((sp->specFlags & needFlags) == needFlags) && + !(sp->specFlags & hateFlags)) { + break; + } + } + } + return sp; +} + +/* Public routines */ + +/* + *--------------------------------------------------------------------------- + * + * Blt_ConfigureWidgetFromObj -- + * + * Process command-line options and database options to + * fill in fields of a widget record with resources and + * other parameters. + * + * Results: + * A standard TCL return value. In case of an error, + * the interp's result will hold an error message. + * + * Side effects: + * The fields of widgRec get filled in with information + * from argc/argv and the option database. Old information + * in widgRec's fields gets recycled. + * + *--------------------------------------------------------------------------- + */ +int +Blt_ConfigureWidgetFromObj( + Tcl_Interp *interp, /* Interpreter for error reporting. */ + Tk_Window tkwin, /* Window containing widget (needed to + * set up X resources). */ + Blt_ConfigSpec *specs, /* Describes legal options. */ + int objc, /* Number of elements in argv. */ + Tcl_Obj *const *objv, /* Command-line options. */ + char *widgRec, /* Record whose fields are to be + * modified. Values must be properly + * initialized. */ + int flags) /* Used to specify additional flags + * that must be present in config specs + * for them to be considered. Also, + * may have BLT_CONFIG_OBJV_ONLY set. */ +{ + Blt_ConfigSpec *sp; + int needFlags; /* Specs must contain this set of flags + * or else they are not considered. */ + int hateFlags; /* If a spec contains any bits here, it's + * not considered. */ + int result; + + if (tkwin == NULL) { + /* + * Either we're not really in Tk, or the main window was destroyed and + * we're on our way out of the application + */ + Tcl_AppendResult(interp, "NULL main window", (char *)NULL); + return TCL_ERROR; + } + + needFlags = flags & ~(BLT_CONFIG_USER_BIT - 1); + if (Tk_Depth(tkwin) <= 1) { + hateFlags = BLT_CONFIG_COLOR_ONLY; + } else { + hateFlags = BLT_CONFIG_MONO_ONLY; + } + + /* + * Pass one: scan through all the option specs, replacing strings + * with Tk_Uid structs (if this hasn't been done already) and + * clearing the BLT_CONFIG_OPTION_SPECIFIED flags. + */ + + for (sp = specs; sp->type != BLT_CONFIG_END; sp++) { + if (!(sp->specFlags & INIT) && (sp->switchName != NULL)) { + if (sp->dbName != NULL) { + sp->dbName = Tk_GetUid(sp->dbName); + } + if (sp->dbClass != NULL) { + sp->dbClass = Tk_GetUid(sp->dbClass); + } + if (sp->defValue != NULL) { + sp->defValue = Tk_GetUid(sp->defValue); + } + } + sp->specFlags = (sp->specFlags & ~BLT_CONFIG_OPTION_SPECIFIED) | INIT; + } + + /* + * Pass two: scan through all of the arguments, processing those + * that match entries in the specs. + */ + while (objc > 0) { + sp = FindConfigSpec(interp, specs, objv[0], needFlags, hateFlags); + if (sp == NULL) { + return TCL_ERROR; + } + + /* Process the entry. */ + if (objc < 2) { + Tcl_AppendResult(interp, "value for \"", Tcl_GetString(objv[0]), + "\" missing", (char *)NULL); + return TCL_ERROR; + } + if (DoConfig(interp, tkwin, sp, objv[1], widgRec) != TCL_OK) { + char msg[100]; + + sprintf_s(msg, 100, "\n (processing \"%.40s\" option)", + sp->switchName); + Tcl_AddErrorInfo(interp, msg); + return TCL_ERROR; + } + sp->specFlags |= BLT_CONFIG_OPTION_SPECIFIED; + objc -= 2, objv += 2; + } + + /* + * Pass three: scan through all of the specs again; if no + * command-line argument matched a spec, then check for info + * in the option database. If there was nothing in the + * database, then use the default. + */ + + if ((flags & BLT_CONFIG_OBJV_ONLY) == 0) { + Tcl_Obj *objPtr; + + for (sp = specs; sp->type != BLT_CONFIG_END; sp++) { + if ((sp->specFlags & BLT_CONFIG_OPTION_SPECIFIED) || + (sp->switchName == NULL) || (sp->type == BLT_CONFIG_SYNONYM)) { + continue; + } + if (((sp->specFlags & needFlags) != needFlags) || + (sp->specFlags & hateFlags)) { + continue; + } + objPtr = NULL; + if (sp->dbName != NULL) { + Tk_Uid value; + + /* If a resource name was specified, check if there's + * also a value was associated with it. This + * overrides the default value. */ + value = Tk_GetOption(tkwin, sp->dbName, sp->dbClass); + if (value != NULL) { + objPtr = Tcl_NewStringObj(value, -1); + } + } + + if (objPtr != NULL) { + Tcl_IncrRefCount(objPtr); + result = DoConfig(interp, tkwin, sp, objPtr, widgRec); + Tcl_DecrRefCount(objPtr); + if (result != TCL_OK) { + char msg[200]; + + sprintf_s(msg, 200, + "\n (%s \"%.50s\" in widget \"%.50s\")", + "database entry for", sp->dbName, Tk_PathName(tkwin)); + Tcl_AddErrorInfo(interp, msg); + return TCL_ERROR; + } + } else if ((sp->defValue != NULL) && + ((sp->specFlags & BLT_CONFIG_DONT_SET_DEFAULT) == 0)) { + + /* No resource value is found, use the default value. */ + objPtr = Tcl_NewStringObj(sp->defValue, -1); + Tcl_IncrRefCount(objPtr); + result = DoConfig(interp, tkwin, sp, objPtr, widgRec); + Tcl_DecrRefCount(objPtr); + if (result != TCL_OK) { + char msg[200]; + + sprintf_s(msg, 200, + "\n (%s \"%.50s\" in widget \"%.50s\")", + "default value for", sp->dbName, Tk_PathName(tkwin)); + Tcl_AddErrorInfo(interp, msg); + return TCL_ERROR; + } + } + } + } + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_ConfigureInfoFromObj -- + * + * Return information about the configuration options + * for a window, and their current values. + * + * Results: + * Always returns TCL_OK. The interp's result will be modified + * hold a description of either a single configuration option + * available for "widgRec" via "specs", or all the configuration + * options available. In the "all" case, the result will + * available for "widgRec" via "specs". The result will + * be a list, each of whose entries describes one option. + * Each entry will itself be a list containing the option's + * name for use on command lines, database name, database + * class, default value, and current value (empty string + * if none). For options that are synonyms, the list will + * contain only two values: name and synonym name. If the + * "name" argument is non-NULL, then the only information + * returned is that for the named argument (i.e. the corresponding + * entry in the overall list is returned). + * + * Side effects: + * None. + * + *--------------------------------------------------------------------------- + */ + +int +Blt_ConfigureInfoFromObj( + Tcl_Interp *interp, /* Interpreter for error reporting. */ + Tk_Window tkwin, /* Window corresponding to widgRec. */ + Blt_ConfigSpec *specs, /* Describes legal options. */ + char *widgRec, /* Record whose fields contain current + * values for options. */ + Tcl_Obj *objPtr, /* If non-NULL, indicates a single option + * whose info is to be returned. Otherwise + * info is returned for all options. */ + int flags) /* Used to specify additional flags + * that must be present in config specs + * for them to be considered. */ +{ + Blt_ConfigSpec *sp; + Tcl_Obj *listObjPtr, *valueObjPtr; + const char *string; + int needFlags, hateFlags; + + needFlags = flags & ~(BLT_CONFIG_USER_BIT - 1); + if (Tk_Depth(tkwin) <= 1) { + hateFlags = BLT_CONFIG_COLOR_ONLY; + } else { + hateFlags = BLT_CONFIG_MONO_ONLY; + } + + /* + * If information is only wanted for a single configuration + * spec, then handle that one spec specially. + */ + + Tcl_SetResult(interp, (char *)NULL, TCL_STATIC); + if (objPtr != NULL) { + sp = FindConfigSpec(interp, specs, objPtr, needFlags, hateFlags); + if (sp == NULL) { + return TCL_ERROR; + } + valueObjPtr = FormatConfigInfo(interp, tkwin, sp, widgRec); + Tcl_SetObjResult(interp, valueObjPtr); + return TCL_OK; + } + + /* + * Loop through all the specs, creating a big list with all + * their information. + */ + string = NULL; /* Suppress compiler warning. */ + if (objPtr != NULL) { + string = Tcl_GetString(objPtr); + } + listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL); + for (sp = specs; sp->type != BLT_CONFIG_END; sp++) { + if ((objPtr != NULL) && (sp->switchName != string)) { + continue; + } + if (((sp->specFlags & needFlags) != needFlags) || + (sp->specFlags & hateFlags)) { + continue; + } + if (sp->switchName == NULL) { + continue; + } + valueObjPtr = FormatConfigInfo(interp, tkwin, sp, widgRec); + Tcl_ListObjAppendElement(interp, listObjPtr, valueObjPtr); + } + Tcl_SetObjResult(interp, listObjPtr); + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_ConfigureValueFromObj -- + * + * This procedure returns the current value of a configuration + * option for a widget. + * + * Results: + * The return value is a standard TCL completion code (TCL_OK or + * TCL_ERROR). The interp's result will be set to hold either the value + * of the option given by objPtr (if TCL_OK is returned) or + * an error message (if TCL_ERROR is returned). + * + * Side effects: + * None. + * + *--------------------------------------------------------------------------- + */ +int +Blt_ConfigureValueFromObj( + Tcl_Interp *interp, /* Interpreter for error reporting. */ + Tk_Window tkwin, /* Window corresponding to widgRec. */ + Blt_ConfigSpec *specs, /* Describes legal options. */ + char *widgRec, /* Record whose fields contain current + * values for options. */ + Tcl_Obj *objPtr, /* Gives the command-line name for the + * option whose value is to be returned. */ + int flags) /* Used to specify additional flags + * that must be present in config specs + * for them to be considered. */ +{ + Blt_ConfigSpec *sp; + int needFlags, hateFlags; + + needFlags = flags & ~(BLT_CONFIG_USER_BIT - 1); + if (Tk_Depth(tkwin) <= 1) { + hateFlags = BLT_CONFIG_COLOR_ONLY; + } else { + hateFlags = BLT_CONFIG_MONO_ONLY; + } + sp = FindConfigSpec(interp, specs, objPtr, needFlags, hateFlags); + if (sp == NULL) { + return TCL_ERROR; + } + objPtr = FormatConfigValue(interp, tkwin, sp, widgRec); + Tcl_SetObjResult(interp, objPtr); + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_FreeOptions -- + * + * Free up all resources associated with configuration options. + * + * Results: + * None. + * + * Side effects: + * Any resource in widgRec that is controlled by a configuration + * option (e.g. a Tk_3DBorder or XColor) is freed in the appropriate + * fashion. + * + *--------------------------------------------------------------------------- + */ +void +Blt_FreeOptions( + Blt_ConfigSpec *specs, /* Describes legal options. */ + char *widgRec, /* Record whose fields contain current + * values for options. */ + Display *display, /* X display; needed for freeing some + * resources. */ + int needFlags) /* Used to specify additional flags + * that must be present in config specs + * for them to be considered. */ +{ + Blt_ConfigSpec *sp; + + for (sp = specs; sp->type != BLT_CONFIG_END; sp++) { + char *ptr; + + if ((sp->specFlags & needFlags) != needFlags) { + continue; + } + ptr = widgRec + sp->offset; + switch (sp->type) { + case BLT_CONFIG_STRING: + if (*((char **) ptr) != NULL) { + Blt_Free(*((char **) ptr)); + *((char **) ptr) = NULL; + } + break; + + case BLT_CONFIG_COLOR: + if (*((XColor **) ptr) != NULL) { + Tk_FreeColor(*((XColor **) ptr)); + *((XColor **) ptr) = NULL; + } + break; + + case BLT_CONFIG_FONT: + if (*((Blt_Font *) ptr) != None) { + Blt_FreeFont(*((Blt_Font *) ptr)); + *((Blt_Font *) ptr) = NULL; + } + break; + + case BLT_CONFIG_TK_FONT: + if (*((Tk_Font *) ptr) != None) { + Tk_FreeFont(*((Tk_Font *) ptr)); + *((Tk_Font *) ptr) = NULL; + } + break; + + case BLT_CONFIG_BITMAP: + if (*((Pixmap *) ptr) != None) { + Tk_FreeBitmap(display, *((Pixmap *) ptr)); + *((Pixmap *) ptr) = None; + } + break; + + case BLT_CONFIG_BORDER: + if (*((Tk_3DBorder *) ptr) != NULL) { + Tk_Free3DBorder(*((Tk_3DBorder *) ptr)); + *((Tk_3DBorder *) ptr) = NULL; + } + break; + + case BLT_CONFIG_CURSOR: + case BLT_CONFIG_ACTIVE_CURSOR: + if (*((Tk_Cursor *) ptr) != None) { + Tk_FreeCursor(display, *((Tk_Cursor *) ptr)); + *((Tk_Cursor *) ptr) = None; + } + break; + + case BLT_CONFIG_OBJ: + if (*(Tcl_Obj **)ptr != NULL) { + Tcl_DecrRefCount(*(Tcl_Obj **)ptr); + *(Tcl_Obj **)ptr = NULL; + } + break; + + case BLT_CONFIG_LIST: + if (*((char ***) ptr) != NULL) { + Blt_Free(*((char ***) ptr)); + *((char ***) ptr) = NULL; + } + break; + + case BLT_CONFIG_UID: + if (*(Blt_Uid *)ptr != NULL) { + Blt_FreeUid(*(Blt_Uid *)ptr); + *(Blt_Uid *)ptr = NULL; + } + break; + + case BLT_CONFIG_BACKGROUND: + if (*((Blt_Background *)ptr) != NULL) { + Blt_FreeBackground(*((Blt_Background *)ptr)); + *((Blt_Background *)ptr) = NULL; + } + break; + + case BLT_CONFIG_CUSTOM: + if ((sp->customPtr->freeProc != NULL) && (*(char **)ptr != NULL)) { + (*sp->customPtr->freeProc)(sp->customPtr->clientData, + display, widgRec, sp->offset); + } + break; + + } + } +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_ConfigModified -- + * + * Given the configuration specifications and one or more option + * patterns (terminated by a NULL), indicate if any of the matching + * configuration options has been reset. + * + * Results: + * Returns 1 if one of the options has changed, 0 otherwise. + * + *--------------------------------------------------------------------------- + */ +int +Blt_ConfigModified TCL_VARARGS_DEF(Blt_ConfigSpec *, arg1) +{ + va_list argList; + Blt_ConfigSpec *specs; + Blt_ConfigSpec *sp; + const char *option; + + specs = TCL_VARARGS_START(Blt_ConfigSpec *, arg1, argList); + while ((option = va_arg(argList, const char *)) != NULL) { + for (sp = specs; sp->type != BLT_CONFIG_END; sp++) { + if ((Tcl_StringMatch(sp->switchName, option)) && + (sp->specFlags & BLT_CONFIG_OPTION_SPECIFIED)) { + va_end(argList); + return 1; + } + } + } + va_end(argList); + return 0; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_ConfigureComponentFromObj -- + * + * Configures a component of a widget. This is useful for + * widgets that have multiple components which aren't uniquely + * identified by a Tk_Window. It allows us, for example, set + * resources for axes of the graph widget. The graph really has + * only one window, but its convenient to specify components in a + * hierarchy of options. + * + * *graph.x.logScale yes + * *graph.Axis.logScale yes + * *graph.temperature.scaleSymbols yes + * *graph.Element.scaleSymbols yes + * + * This is really a hack to work around the limitations of the Tk + * resource database. It creates a temporary window, needed to + * call Tk_ConfigureWidget, using the name of the component. + * + * Results: + * A standard TCL result. + * + * Side Effects: + * A temporary window is created merely to pass to Tk_ConfigureWidget. + * + *--------------------------------------------------------------------------- + */ +int +Blt_ConfigureComponentFromObj( + Tcl_Interp *interp, + Tk_Window parent, /* Window to associate with component */ + const char *name, /* Name of component */ + const char *className, + Blt_ConfigSpec *sp, + int objc, + Tcl_Obj *const *objv, + char *widgRec, + int flags) +{ + Tk_Window tkwin; + int result; + char *tmpName; + int isTemporary = FALSE; + + tmpName = Blt_AssertStrdup(name); + + /* Window name can't start with an upper case letter */ + tmpName[0] = tolower(name[0]); + + /* + * Create component if a child window by the component's name + * doesn't already exist. + */ + tkwin = Blt_FindChild(parent, tmpName); + if (tkwin == NULL) { + tkwin = Tk_CreateWindow(interp, parent, tmpName, (char *)NULL); + isTemporary = TRUE; + } + if (tkwin == NULL) { + Tcl_AppendResult(interp, "can't find window in \"", + Tk_PathName(parent), "\"", (char *)NULL); + return TCL_ERROR; + } + assert(Tk_Depth(tkwin) == Tk_Depth(parent)); + Blt_Free(tmpName); + + Tk_SetClass(tkwin, className); + result = Blt_ConfigureWidgetFromObj(interp, tkwin, sp, objc, objv, widgRec, + flags); + if (isTemporary) { + Tk_DestroyWindow(tkwin); + } + return result; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_ObjIsOption -- + * + * Indicates whether objPtr is a valid configuration option + * such as -background. + * + * Results: + * Returns 1 is a matching option is found and 0 otherwise. + * + *--------------------------------------------------------------------------- + */ +int +Blt_ObjIsOption( + Blt_ConfigSpec *specs, /* Describes legal options. */ + Tcl_Obj *objPtr, /* Command-line option name. */ + int flags) /* Used to specify additional flags + * that must be present in config specs + * for them to be considered. Also, + * may have BLT_CONFIG_OBJV_ONLY set. */ +{ + Blt_ConfigSpec *sp; + int needFlags; /* Specs must contain this set of flags + * or else they are not considered. */ + + needFlags = flags & ~(BLT_CONFIG_USER_BIT - 1); + sp = FindConfigSpec((Tcl_Interp *)NULL, specs, objPtr, needFlags, 0); + return (sp != NULL); +} diff --git a/src/bltConfig.h b/src/bltConfig.h new file mode 100644 index 0000000..7a19e9d --- /dev/null +++ b/src/bltConfig.h @@ -0,0 +1,406 @@ + +/* + * bltConfig.h -- + * + * Copyright 1993-2004 George A Howlett. + * + * 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 AUTHORS + * OR COPYRIGHT HOLDERS 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. + */ + +#ifndef BLT_CONFIG_H +#define BLT_CONFIG_H + +#ifdef HAVE_STDDEF_H +# include +#endif /* HAVE_STDDEF_H */ + +#ifndef Blt_Offset +#ifdef offsetof +#define Blt_Offset(type, field) ((int) offsetof(type, field)) +#else +#define Blt_Offset(type, field) ((int) ((char *) &((type *) 0)->field)) +#endif +#endif /* Blt_Offset */ + + +typedef int (Blt_OptionParseProc)(ClientData clientData, Tcl_Interp *interp, + Tk_Window tkwin, Tcl_Obj *objPtr, char *widgRec, int offset, int flags); +typedef Tcl_Obj *(Blt_OptionPrintProc)(ClientData clientData, + Tcl_Interp *interp, Tk_Window tkwin, char *widgRec, int offset, + int flags); +typedef void (Blt_OptionFreeProc)(ClientData clientData, Display *display, + char *widgRec, int offset); + +typedef struct Blt_CustomOption { + Blt_OptionParseProc *parseProc; /* Procedure to call to parse + * an option and store it in + * converted form. */ + + Blt_OptionPrintProc *printProc; /* Procedure to return a + * Tcl_Obj representing an + * existing option value. */ + + Blt_OptionFreeProc *freeProc; /* Procedure used to free the + * value. */ + + ClientData clientData; /* Arbitrary one-word value + * used by option parser: + * passed to parseProc and + * printProc. */ +} Blt_CustomOption; + +/* + * Structure used to specify information for Tk_ConfigureWidget. Each + * structure gives complete information for one option, including + * how the option is specified on the command line, where it appears + * in the option database, etc. + */ + +typedef struct { + int type; /* Type of option, such as + * BLT_CONFIG_COLOR; see definitions + * below. Last option in table must + * have type BLT_CONFIG_END. */ + + const char *switchName; /* Switch used to specify option in + * argv. NULL means this spec is part + * of a group. */ + + Tk_Uid dbName; /* Name for option in option + * database. */ + + Tk_Uid dbClass; /* Class for option in database. */ + + Tk_Uid defValue; /* Default value for option if not + * specified in command line or + * database. */ + + int offset; /* Where in widget record to store + * value; use Blt_Offset macro to + * generate values for this. */ + + int specFlags; /* Any combination of the values + * defined below; other bits are used + * internally by tkConfig.c. */ + + Blt_CustomOption *customPtr; /* If type is BLT_CONFIG_CUSTOM then + * this is a pointer to info about how + * to parse and print the option. + * Otherwise it is irrelevant. */ +} Blt_ConfigSpec; + +/* + * Type values for Blt_ConfigSpec structures. See the user + * documentation for details. + */ +typedef enum { + BLT_CONFIG_ACTIVE_CURSOR, + BLT_CONFIG_ANCHOR, + BLT_CONFIG_BITMAP, + BLT_CONFIG_BOOLEAN, + BLT_CONFIG_BORDER, + BLT_CONFIG_CAP_STYLE, + BLT_CONFIG_COLOR, + BLT_CONFIG_CURSOR, + BLT_CONFIG_CUSTOM, + BLT_CONFIG_DOUBLE, + BLT_CONFIG_FONT, + BLT_CONFIG_INT, + BLT_CONFIG_JOIN_STYLE, + BLT_CONFIG_JUSTIFY, + BLT_CONFIG_MM, + BLT_CONFIG_RELIEF, + BLT_CONFIG_STRING, + BLT_CONFIG_SYNONYM, + BLT_CONFIG_UID, + BLT_CONFIG_WINDOW, + + BLT_CONFIG_BITMASK, + BLT_CONFIG_BITMASK_INVERT, + BLT_CONFIG_DASHES, + BLT_CONFIG_FILL, + BLT_CONFIG_FLOAT, + BLT_CONFIG_INT_NNEG, /* 0..N */ + BLT_CONFIG_INT_POS, /* 1..N */ + BLT_CONFIG_LIST, + BLT_CONFIG_LONG, + BLT_CONFIG_LONG_NNEG, /* 0..N */ + BLT_CONFIG_LONG_POS, /* 1..N */ + BLT_CONFIG_OBJ, + BLT_CONFIG_PAD, + BLT_CONFIG_PIXELS_NNEG, /* 1.1c 2m 3.2i excluding negative + values. */ + BLT_CONFIG_PIXELS_POS, /* 1.1c 2m 3.2i excluding negative + * values and zero. */ + BLT_CONFIG_PIXELS, /* 1.1c 2m 3.2i. */ + BLT_CONFIG_RESIZE, + BLT_CONFIG_SIDE, + BLT_CONFIG_STATE, + BLT_CONFIG_BACKGROUND, + BLT_CONFIG_PIX32, + + BLT_CONFIG_TK_FONT, + BLT_CONFIG_END +} Blt_ConfigTypes; + +/* + * Possible values for flags argument to Tk_ConfigureWidget: + */ +#define BLT_CONFIG_OBJV_ONLY 1 + +/* + * Possible flag values for Blt_ConfigSpec structures. Any bits at or + * above BLT_CONFIG_USER_BIT may be used by clients for selecting + * certain entries. Before changing any values here, coordinate with + * tkOldConfig.c (internal-use-only flags are defined there). + */ +/* + * Values for "flags" field of Blt_ConfigSpec structures. Be sure to + * coordinate these values with those defined in tk.h + * (BLT_CONFIG_COLOR_ONLY, etc.). There must not be overlap! + * + * INIT - Non-zero means (char *) things have been + * converted to Tk_Uid's. + */ +#define INIT (1<<0) +#define BLT_CONFIG_NULL_OK (1<<1) +#define BLT_CONFIG_COLOR_ONLY (1<<2) +#define BLT_CONFIG_MONO_ONLY (1<<3) +#define BLT_CONFIG_DONT_SET_DEFAULT (1<<4) +#define BLT_CONFIG_OPTION_SPECIFIED (1<<5) +#define BLT_CONFIG_USER_BIT (1<<8) + + +#define SIDE_LEFT (1<<0) +#define SIDE_TOP (1<<1) +#define SIDE_RIGHT (1<<2) +#define SIDE_BOTTOM (1<<3) + +#define STATE_NORMAL (0) +#define STATE_ACTIVE (1<<0) +#define STATE_DISABLED (1<<1) +#define STATE_EMPHASIS (1<<2) + +/* + *--------------------------------------------------------------------------- + * + * Blt_Pad -- + * + * Specifies vertical and horizontal padding. + * + * Padding can be specified on a per side basis. The fields + * side1 and side2 refer to the opposite sides, either + * horizontally or vertically. + * + * side1 side2 + * ----- ----- + * x | left right + * y | top bottom + * + *--------------------------------------------------------------------------- + */ +typedef struct { + unsigned short int side1, side2; +} Blt_Pad; + +#define padLeft xPad.side1 +#define padRight xPad.side2 +#define padTop yPad.side1 +#define padBottom yPad.side2 +#define PADDING(x) ((x).side1 + (x).side2) + +/* + *--------------------------------------------------------------------------- + * + * The following enumerated values are used as bit flags. + * FILL_NONE Neither coordinate plane is specified + * FILL_X Horizontal plane. + * FILL_Y Vertical plane. + * FILL_BOTH Both vertical and horizontal planes. + * + *--------------------------------------------------------------------------- + */ +#define FILL_NONE 0 +#define FILL_X 1 +#define FILL_Y 2 +#define FILL_BOTH 3 + +/* + * Resize -- + * + * These flags indicate in what ways each partition in a table can be + * resized from its default dimensions. The normal size of a row/column + * is the minimum amount of space needed to hold the widgets that span + * it. The table may then be stretched or shrunk depending if the + * container is larger or smaller than the table. This can occur if 1) + * the user resizes the toplevel widget, or 2) the container is in turn + * packed into a larger widget and the "fill" option is set. + * + * RESIZE_NONE - No resizing from normal size. + * RESIZE_EXPAND - Do not allow the size to decrease. + * The size may increase however. + * RESIZE_SHRINK - Do not allow the size to increase. + * The size may decrease however. + * RESIZE_BOTH - Allow the size to increase or + * decrease from the normal size. + * RESIZE_VIRGIN - Special case of the resize flag. Used to + * indicate the initial state of the flag. + * Empty rows/columns are treated differently + * if this row/column is set. + */ + +#define RESIZE_NONE 0 +#define RESIZE_EXPAND (1<<0) +#define RESIZE_SHRINK (1<<1) +#define RESIZE_BOTH (RESIZE_EXPAND | RESIZE_SHRINK) + +/* + *--------------------------------------------------------------------------- + * + * Blt_Dashes -- + * + * List of dash values (maximum 11 based upon PostScript limit). + * + *--------------------------------------------------------------------------- + */ +typedef struct { + unsigned char values[12]; + int offset; +} Blt_Dashes; + +#define LineIsDashed(d) ((d).values[0] != 0) + +/* + * Blt_Limits -- + * + * Defines the bounding of a size (width or height) in the paneset. It may + * be related to the widget, pane or paneset size. + */ +typedef struct { + int flags; /* Flags indicate whether using default + * values for limits or not. See flags + * below. */ + int max, min; /* Values for respective limits. */ + int nom; /* Nominal starting value. */ +} Blt_Limits; + +#define LIMITS_MIN_SET (1<<0) +#define LIMITS_MAX_SET (1<<1) +#define LIMITS_NOM_SET (1<<2) + +#define LIMITS_MIN 0 /* Default minimum limit */ +#define LIMITS_MAX SHRT_MAX /* Default maximum limit */ +#define LIMITS_NOM -1000 /* Default nomimal value. Indicates + * if a pane has received any space + * yet */ + +BLT_EXTERN void Blt_SetDashes (Display *display, GC gc, Blt_Dashes *dashesPtr); +BLT_EXTERN Blt_Dashes *Blt_GetDashes (GC gc); + +BLT_EXTERN void Blt_ResetLimits(Blt_Limits *limitsPtr); +BLT_EXTERN int Blt_GetLimitsFromObj(Tcl_Interp *interp, Tk_Window tkwin, + Tcl_Obj *objPtr, Blt_Limits *limitsPtr); + +BLT_EXTERN int Blt_ConfigureInfoFromObj(Tcl_Interp *interp, Tk_Window tkwin, + Blt_ConfigSpec *specs, char *widgRec, Tcl_Obj *objPtr, int flags); + +BLT_EXTERN int Blt_ConfigureValueFromObj(Tcl_Interp *interp, Tk_Window tkwin, + Blt_ConfigSpec *specs, char *widgRec, Tcl_Obj *objPtr, int flags); + +BLT_EXTERN int Blt_ConfigureWidgetFromObj(Tcl_Interp *interp, Tk_Window tkwin, + Blt_ConfigSpec *specs, int objc, Tcl_Obj *const *objv, char *widgRec, + int flags); + +BLT_EXTERN int Blt_ConfigureComponentFromObj(Tcl_Interp *interp, + Tk_Window tkwin, const char *name, const char *className, + Blt_ConfigSpec *specs, int objc, Tcl_Obj *const *objv, char *widgRec, + int flags); + +BLT_EXTERN int Blt_ConfigModified TCL_VARARGS(Blt_ConfigSpec *, specs); + +BLT_EXTERN const char *Blt_NameOfState(int state); +BLT_EXTERN const char *Blt_NameOfSide(int side); + +BLT_EXTERN void Blt_FreeOptions(Blt_ConfigSpec *specs, char *widgRec, + Display *display, int needFlags); + +BLT_EXTERN int Blt_ObjIsOption(Blt_ConfigSpec *specs, Tcl_Obj *objPtr, + int flags); + +BLT_EXTERN int Blt_GetSideFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, + int *sidePtr); + +BLT_EXTERN int Blt_GetPositionFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, + long *indexPtr); + +BLT_EXTERN int Blt_GetPixelsFromObj(Tcl_Interp *interp, Tk_Window tkwin, + Tcl_Obj *objPtr, int flags, int *valuePtr); + +BLT_EXTERN int Blt_GetPadFromObj(Tcl_Interp *interp, Tk_Window tkwin, + Tcl_Obj *objPtr, Blt_Pad *padPtr); + +BLT_EXTERN int Blt_GetStateFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, + int *statePtr); + +BLT_EXTERN int Blt_GetFillFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, + int *fillPtr); + +BLT_EXTERN int Blt_GetResizeFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, + int *fillPtr); + +BLT_EXTERN int Blt_GetDashesFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, + Blt_Dashes *dashesPtr); + +#if (_TK_VERSION < _VERSION(8,1,0)) +BLT_EXTERN int Tk_GetAnchorFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, + Tk_Anchor *anchorPtr); + +BLT_EXTERN int Tk_GetJustifyFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, + Tk_Justify *justifyPtr); + +BLT_EXTERN int Tk_GetReliefFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, + int *reliefPtr); + +BLT_EXTERN int Tk_GetMMFromObj(Tcl_Interp *interp, Tk_Window tkwin, + Tcl_Obj *objPtr, double *doublePtr); + +BLT_EXTERN int Tk_GetPixelsFromObj(Tcl_Interp *interp, Tk_Window tkwin, + Tcl_Obj *objPtr, int *intPtr); + +BLT_EXTERN Tk_3DBorder Tk_Alloc3DBorderFromObj(Tcl_Interp *interp, + Tk_Window tkwin, Tcl_Obj *objPtr); + +BLT_EXTERN Pixmap Tk_AllocBitmapFromObj(Tcl_Interp *interp, Tk_Window tkwin, + Tcl_Obj *objPtr); + +BLT_EXTERN Tk_Font Tk_AllocFontFromObj(Tcl_Interp *interp, Tk_Window tkwin, + Tcl_Obj *objPtr); + +BLT_EXTERN Tk_Cursor Tk_AllocCursorFromObj(Tcl_Interp *interp, Tk_Window tkwin, + Tcl_Obj *objPtr); + +BLT_EXTERN XColor *Tk_AllocColorFromObj(Tcl_Interp *interp, Tk_Window tkwin, + Tcl_Obj *objPtr); +#endif /* < 8.1 */ + +#endif /* BLT_CONFIG_H */ diff --git a/src/bltGrAxis.C b/src/bltGrAxis.C new file mode 100644 index 0000000..f6f54e9 --- /dev/null +++ b/src/bltGrAxis.C @@ -0,0 +1,5765 @@ + +/* + * bltGrAxis.c -- + * + * This module implements coordinate axes for the BLT graph widget. + * + * Copyright 1993-2004 George A Howlett. + * + * 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 AUTHORS OR COPYRIGHT HOLDERS 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. + */ + +#include "bltGraph.h" +#include "bltOp.h" +#include "bltGrElem.h" +#include + +#define MAXTICKS 10001 + +#define FCLAMP(x) ((((x) < 0.0) ? 0.0 : ((x) > 1.0) ? 1.0 : (x))) + +/* + * Round x in terms of units + */ +#define UROUND(x,u) (Round((x)/(u))*(u)) +#define UCEIL(x,u) (ceil((x)/(u))*(u)) +#define UFLOOR(x,u) (floor((x)/(u))*(u)) + +#define NUMDIGITS 15 /* Specifies the number of digits of + * accuracy used when outputting axis + * tick labels. */ +enum TickRange { + AXIS_TIGHT, AXIS_LOOSE, AXIS_ALWAYS_LOOSE +}; + +#define AXIS_PAD_TITLE 2 /* Padding for axis title. */ + +/* Axis flags: */ + +#define AXIS_AUTO_MAJOR (1<<16) /* Auto-generate major ticks. */ +#define AXIS_AUTO_MINOR (1<<17) /* Auto-generate minor ticks. */ +#define AXIS_USE (1<<18) /* Axis is displayed on the screen via + * the "use" operation */ +#define AXIS_GRID (1<<19) /* Display grid lines. */ +#define AXIS_GRIDMINOR (1<<20) /* Display grid lines for minor + * ticks. */ +#define AXIS_SHOWTICKS (1<<21) /* Display axis ticks. */ +#define AXIS_EXTERIOR (1<<22) /* Axis is exterior to the plot. */ +#define AXIS_CHECK_LIMITS (1<<23) /* Validate user-defined axis limits. */ + +#define HORIZMARGIN(m) (!((m)->site & 0x1)) /* Even sites are horizontal */ + +typedef struct { + int axis; /* Length of the axis. */ + int t1; /* Length of a major tick (in + * pixels). */ + int t2; /* Length of a minor tick (in + * pixels). */ + int label; /* Distance from axis to tick label. */ +} AxisInfo; + +typedef struct { + const char *name; + ClassId classId; + int margin, invertMargin; +} AxisName; + +static AxisName axisNames[] = { + { "x", CID_AXIS_X, MARGIN_BOTTOM, MARGIN_LEFT }, + { "y", CID_AXIS_Y, MARGIN_LEFT, MARGIN_BOTTOM }, + { "x2", CID_AXIS_X, MARGIN_TOP, MARGIN_RIGHT }, + { "y2", CID_AXIS_Y, MARGIN_RIGHT, MARGIN_TOP } +} ; +static int nAxisNames = sizeof(axisNames) / sizeof(AxisName); + +static Blt_OptionParseProc ObjToLimitProc; +static Blt_OptionPrintProc LimitToObjProc; +static Blt_CustomOption limitOption = { + ObjToLimitProc, LimitToObjProc, NULL, (ClientData)0 +}; + +static Blt_OptionFreeProc FreeTicksProc; +static Blt_OptionParseProc ObjToTicksProc; +static Blt_OptionPrintProc TicksToObjProc; +static Blt_CustomOption majorTicksOption = { + ObjToTicksProc, TicksToObjProc, FreeTicksProc, (ClientData)AXIS_AUTO_MAJOR, +}; +static Blt_CustomOption minorTicksOption = { + ObjToTicksProc, TicksToObjProc, FreeTicksProc, (ClientData)AXIS_AUTO_MINOR, +}; +static Blt_OptionFreeProc FreeAxisProc; +static Blt_OptionPrintProc AxisToObjProc; +static Blt_OptionParseProc ObjToAxisProc; +Blt_CustomOption bltXAxisOption = { + ObjToAxisProc, AxisToObjProc, FreeAxisProc, (ClientData)CID_AXIS_X +}; +Blt_CustomOption bltYAxisOption = { + ObjToAxisProc, AxisToObjProc, FreeAxisProc, (ClientData)CID_AXIS_Y +}; + +static Blt_OptionFreeProc FreeFormatProc; +static Blt_OptionParseProc ObjToFormatProc; +static Blt_OptionPrintProc FormatToObjProc; +static Blt_CustomOption formatOption = { + ObjToFormatProc, FormatToObjProc, FreeFormatProc, (ClientData)0, +}; +static Blt_OptionParseProc ObjToLooseProc; +static Blt_OptionPrintProc LooseToObjProc; +static Blt_CustomOption looseOption = { + ObjToLooseProc, LooseToObjProc, NULL, (ClientData)0, +}; + +static Blt_OptionParseProc ObjToUseProc; +static Blt_OptionPrintProc UseToObjProc; +static Blt_CustomOption useOption = { + ObjToUseProc, UseToObjProc, NULL, (ClientData)0 +}; + +#define DEF_AXIS_ACTIVEBACKGROUND STD_ACTIVE_BACKGROUND +#define DEF_AXIS_ACTIVEFOREGROUND STD_ACTIVE_FOREGROUND +#define DEF_AXIS_ACTIVERELIEF "flat" +#define DEF_AXIS_ANGLE "0.0" +#define DEF_AXIS_BACKGROUND (char *)NULL +#define DEF_AXIS_BORDERWIDTH "0" +#define DEF_AXIS_CHECKLIMITS "0" +#define DEF_AXIS_COMMAND (char *)NULL +#define DEF_AXIS_DESCENDING "0" +#define DEF_AXIS_FOREGROUND RGB_BLACK +#define DEF_AXIS_GRID_BARCHART "1" +#define DEF_AXIS_GRIDCOLOR RGB_GREY64 +#define DEF_AXIS_GRIDDASHES "dot" +#define DEF_AXIS_GRID_GRAPH "0" +#define DEF_AXIS_GRIDLINEWIDTH "0" +#define DEF_AXIS_GRIDMINOR "1" +#define DEF_AXIS_GRIDMINOR_COLOR RGB_GREY64 +#define DEF_AXIS_HIDE "0" +#define DEF_AXIS_JUSTIFY "c" +#define DEF_AXIS_LIMITS_FORMAT (char *)NULL +#define DEF_AXIS_LINEWIDTH "1" +#define DEF_AXIS_LOGSCALE "0" +#define DEF_AXIS_LOOSE "0" +#define DEF_AXIS_RANGE "0.0" +#define DEF_AXIS_RELIEF "flat" +#define DEF_AXIS_SCROLL_INCREMENT "10" +#define DEF_AXIS_SHIFTBY "0.0" +#define DEF_AXIS_SHOWTICKS "1" +#define DEF_AXIS_STEP "0.0" +#define DEF_AXIS_STEP "0.0" +#define DEF_AXIS_SUBDIVISIONS "2" +#define DEF_AXIS_TAGS "all" +#define DEF_AXIS_EXTERIOR "1" +#define DEF_AXIS_TICK_ANCHOR "c" +#define DEF_AXIS_LIMITS_FONT STD_FONT_NUMBERS +#define DEF_AXIS_TICKFONT_GRAPH STD_FONT_NUMBERS +#define DEF_AXIS_TICKFONT_BARCHART STD_FONT_SMALL +#define DEF_AXIS_TICKLENGTH "4" +#define DEF_AXIS_DIVISIONS "10" +#define DEF_AXIS_TITLE_ALTERNATE "0" +#define DEF_AXIS_TITLE_FG RGB_BLACK +#define DEF_AXIS_TITLE_FONT "{Sans Serif} 10" +#define DEF_AXIS_X_STEP_BARCHART "1.0" +#define DEF_AXIS_X_SUBDIVISIONS_BARCHART "0" + +static Blt_ConfigSpec configSpecs[] = +{ + {BLT_CONFIG_BACKGROUND, "-activebackground", "activeBackground", + "ActiveBackground", DEF_AXIS_ACTIVEBACKGROUND, + Blt_Offset(Axis, activeBg), ALL_GRAPHS | BLT_CONFIG_NULL_OK}, + {BLT_CONFIG_COLOR, "-activeforeground", "activeForeground", + "ActiveForeground", DEF_AXIS_ACTIVEFOREGROUND, + Blt_Offset(Axis, activeFgColor), ALL_GRAPHS}, + {BLT_CONFIG_RELIEF, "-activerelief", "activeRelief", "Relief", + DEF_AXIS_ACTIVERELIEF, Blt_Offset(Axis, activeRelief), + ALL_GRAPHS | BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_DOUBLE, "-autorange", "autoRange", "AutoRange", + DEF_AXIS_RANGE, Blt_Offset(Axis, windowSize), + ALL_GRAPHS | BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_BACKGROUND, "-background", "background", "Background", + DEF_AXIS_BACKGROUND, Blt_Offset(Axis, normalBg), + ALL_GRAPHS | BLT_CONFIG_NULL_OK}, + {BLT_CONFIG_SYNONYM, "-bg", "background", (char *)NULL, (char *)NULL, 0, 0}, + {BLT_CONFIG_LIST, "-bindtags", "bindTags", "BindTags", DEF_AXIS_TAGS, + Blt_Offset(Axis, obj.tags), ALL_GRAPHS | BLT_CONFIG_NULL_OK}, + {BLT_CONFIG_SYNONYM, "-bd", "borderWidth", (char *)NULL, (char *)NULL, + 0, ALL_GRAPHS}, + {BLT_CONFIG_PIXELS_NNEG, "-borderwidth", "borderWidth", "BorderWidth", + DEF_AXIS_BORDERWIDTH, Blt_Offset(Axis, borderWidth), + ALL_GRAPHS | BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_BITMASK, "-checklimits", "checkLimits", "CheckLimits", + DEF_AXIS_CHECKLIMITS, Blt_Offset(Axis, flags), + ALL_GRAPHS | BLT_CONFIG_DONT_SET_DEFAULT, + (Blt_CustomOption *)AXIS_CHECK_LIMITS}, + {BLT_CONFIG_COLOR, "-color", "color", "Color", + DEF_AXIS_FOREGROUND, Blt_Offset(Axis, tickColor), ALL_GRAPHS}, + {BLT_CONFIG_STRING, "-command", "command", "Command", + DEF_AXIS_COMMAND, Blt_Offset(Axis, formatCmd), + BLT_CONFIG_NULL_OK | ALL_GRAPHS}, + {BLT_CONFIG_BOOLEAN, "-descending", "descending", "Descending", + DEF_AXIS_DESCENDING, Blt_Offset(Axis, descending), + ALL_GRAPHS | BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_BITMASK, "-exterior", "exterior", "exterior", DEF_AXIS_EXTERIOR, + Blt_Offset(Axis, flags), ALL_GRAPHS | BLT_CONFIG_DONT_SET_DEFAULT, + (Blt_CustomOption *)AXIS_EXTERIOR}, + {BLT_CONFIG_SYNONYM, "-fg", "color", (char *)NULL, + (char *)NULL, 0, ALL_GRAPHS}, + {BLT_CONFIG_SYNONYM, "-foreground", "color", (char *)NULL, + (char *)NULL, 0, ALL_GRAPHS}, + {BLT_CONFIG_BITMASK, "-grid", "grid", "Grid", DEF_AXIS_GRID_BARCHART, + Blt_Offset(Axis, flags), BARCHART, (Blt_CustomOption *)AXIS_GRID}, + {BLT_CONFIG_BITMASK, "-grid", "grid", "Grid", DEF_AXIS_GRID_GRAPH, + Blt_Offset(Axis, flags), GRAPH | STRIPCHART, + (Blt_CustomOption *)AXIS_GRID}, + {BLT_CONFIG_COLOR, "-gridcolor", "gridColor", "GridColor", + DEF_AXIS_GRIDCOLOR, Blt_Offset(Axis, major.color), ALL_GRAPHS}, + {BLT_CONFIG_DASHES, "-griddashes", "gridDashes", "GridDashes", + DEF_AXIS_GRIDDASHES, Blt_Offset(Axis, major.dashes), + BLT_CONFIG_NULL_OK | ALL_GRAPHS}, + {BLT_CONFIG_PIXELS_NNEG, "-gridlinewidth", "gridLineWidth", + "GridLineWidth", DEF_AXIS_GRIDLINEWIDTH, + Blt_Offset(Axis, major.lineWidth), + BLT_CONFIG_DONT_SET_DEFAULT | ALL_GRAPHS}, + {BLT_CONFIG_BITMASK, "-gridminor", "gridMinor", "GridMinor", + DEF_AXIS_GRIDMINOR, Blt_Offset(Axis, flags), + BLT_CONFIG_DONT_SET_DEFAULT | ALL_GRAPHS, + (Blt_CustomOption *)AXIS_GRIDMINOR}, + {BLT_CONFIG_COLOR, "-gridminorcolor", "gridMinorColor", "GridColor", + DEF_AXIS_GRIDMINOR_COLOR, Blt_Offset(Axis, minor.color), ALL_GRAPHS}, + {BLT_CONFIG_DASHES, "-gridminordashes", "gridMinorDashes", "GridDashes", + DEF_AXIS_GRIDDASHES, Blt_Offset(Axis, minor.dashes), + BLT_CONFIG_NULL_OK | ALL_GRAPHS}, + {BLT_CONFIG_PIXELS_NNEG, "-gridminorlinewidth", "gridMinorLineWidth", + "GridLineWidth", DEF_AXIS_GRIDLINEWIDTH, + Blt_Offset(Axis, minor.lineWidth), + BLT_CONFIG_DONT_SET_DEFAULT | ALL_GRAPHS}, + {BLT_CONFIG_BITMASK, "-hide", "hide", "Hide", DEF_AXIS_HIDE, + Blt_Offset(Axis, flags), ALL_GRAPHS | BLT_CONFIG_DONT_SET_DEFAULT, + (Blt_CustomOption *)HIDE}, + {BLT_CONFIG_JUSTIFY, "-justify", "justify", "Justify", + DEF_AXIS_JUSTIFY, Blt_Offset(Axis, titleJustify), + ALL_GRAPHS | BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_BOOLEAN, "-labeloffset", "labelOffset", "LabelOffset", + (char *)NULL, Blt_Offset(Axis, labelOffset), ALL_GRAPHS}, + {BLT_CONFIG_COLOR, "-limitscolor", "limitsColor", "Color", + DEF_AXIS_FOREGROUND, Blt_Offset(Axis, limitsTextStyle.color), + ALL_GRAPHS}, + {BLT_CONFIG_FONT, "-limitsfont", "limitsFont", "Font", DEF_AXIS_LIMITS_FONT, + Blt_Offset(Axis, limitsTextStyle.font), ALL_GRAPHS}, + {BLT_CONFIG_CUSTOM, "-limitsformat", "limitsFormat", "LimitsFormat", + (char *)NULL, Blt_Offset(Axis, limitsFormats), + BLT_CONFIG_NULL_OK | ALL_GRAPHS, &formatOption}, + {BLT_CONFIG_PIXELS_NNEG, "-linewidth", "lineWidth", "LineWidth", + DEF_AXIS_LINEWIDTH, Blt_Offset(Axis, lineWidth), + ALL_GRAPHS | BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_BOOLEAN, "-logscale", "logScale", "LogScale", + DEF_AXIS_LOGSCALE, Blt_Offset(Axis, logScale), + ALL_GRAPHS | BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_CUSTOM, "-loose", "loose", "Loose", DEF_AXIS_LOOSE, 0, + ALL_GRAPHS | BLT_CONFIG_DONT_SET_DEFAULT, &looseOption}, + {BLT_CONFIG_CUSTOM, "-majorticks", "majorTicks", "MajorTicks", + (char *)NULL, Blt_Offset(Axis, t1Ptr), + BLT_CONFIG_NULL_OK | ALL_GRAPHS, &majorTicksOption}, + {BLT_CONFIG_CUSTOM, "-max", "max", "Max", (char *)NULL, + Blt_Offset(Axis, reqMax), ALL_GRAPHS, &limitOption}, + {BLT_CONFIG_CUSTOM, "-min", "min", "Min", (char *)NULL, + Blt_Offset(Axis, reqMin), ALL_GRAPHS, &limitOption}, + {BLT_CONFIG_CUSTOM, "-minorticks", "minorTicks", "MinorTicks", + (char *)NULL, Blt_Offset(Axis, t2Ptr), + BLT_CONFIG_NULL_OK | ALL_GRAPHS, &minorTicksOption}, + {BLT_CONFIG_RELIEF, "-relief", "relief", "Relief", + DEF_AXIS_RELIEF, Blt_Offset(Axis, relief), + ALL_GRAPHS | BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_FLOAT, "-rotate", "rotate", "Rotate", DEF_AXIS_ANGLE, + Blt_Offset(Axis, tickAngle), ALL_GRAPHS | BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_OBJ, "-scrollcommand", "scrollCommand", "ScrollCommand", + (char *)NULL, Blt_Offset(Axis, scrollCmdObjPtr), + ALL_GRAPHS | BLT_CONFIG_NULL_OK}, + {BLT_CONFIG_PIXELS_POS, "-scrollincrement", "scrollIncrement", + "ScrollIncrement", DEF_AXIS_SCROLL_INCREMENT, + Blt_Offset(Axis, scrollUnits), ALL_GRAPHS|BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_CUSTOM, "-scrollmax", "scrollMax", "ScrollMax", (char *)NULL, + Blt_Offset(Axis, reqScrollMax), ALL_GRAPHS, &limitOption}, + {BLT_CONFIG_CUSTOM, "-scrollmin", "scrollMin", "ScrollMin", (char *)NULL, + Blt_Offset(Axis, reqScrollMin), ALL_GRAPHS, &limitOption}, + {BLT_CONFIG_DOUBLE, "-shiftby", "shiftBy", "ShiftBy", + DEF_AXIS_SHIFTBY, Blt_Offset(Axis, shiftBy), + ALL_GRAPHS | BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_BITMASK, "-showticks", "showTicks", "ShowTicks", + DEF_AXIS_SHOWTICKS, Blt_Offset(Axis, flags), + ALL_GRAPHS | BLT_CONFIG_DONT_SET_DEFAULT, + (Blt_CustomOption *)AXIS_SHOWTICKS}, + {BLT_CONFIG_DOUBLE, "-stepsize", "stepSize", "StepSize", + DEF_AXIS_STEP, Blt_Offset(Axis, reqStep), + ALL_GRAPHS | BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_INT, "-subdivisions", "subdivisions", "Subdivisions", + DEF_AXIS_SUBDIVISIONS, Blt_Offset(Axis, reqNumMinorTicks), ALL_GRAPHS}, + {BLT_CONFIG_ANCHOR, "-tickanchor", "tickAnchor", "Anchor", + DEF_AXIS_TICK_ANCHOR, Blt_Offset(Axis, reqTickAnchor), ALL_GRAPHS}, + {BLT_CONFIG_FONT, "-tickfont", "tickFont", "Font", + DEF_AXIS_TICKFONT_GRAPH, Blt_Offset(Axis, tickFont), + GRAPH | STRIPCHART}, + {BLT_CONFIG_FONT, "-tickfont", "tickFont", "Font", + DEF_AXIS_TICKFONT_BARCHART, Blt_Offset(Axis, tickFont), BARCHART}, + {BLT_CONFIG_PIXELS_NNEG, "-ticklength", "tickLength", "TickLength", + DEF_AXIS_TICKLENGTH, Blt_Offset(Axis, tickLength), + ALL_GRAPHS | BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_INT, "-tickdefault", "tickDefault", "TickDefault", + DEF_AXIS_DIVISIONS, Blt_Offset(Axis, reqNumMajorTicks), + ALL_GRAPHS | BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_STRING, "-title", "title", "Title", + (char *)NULL, Blt_Offset(Axis, title), + BLT_CONFIG_DONT_SET_DEFAULT | BLT_CONFIG_NULL_OK | ALL_GRAPHS}, + {BLT_CONFIG_BOOLEAN, "-titlealternate", "titleAlternate", "TitleAlternate", + DEF_AXIS_TITLE_ALTERNATE, Blt_Offset(Axis, titleAlternate), + BLT_CONFIG_DONT_SET_DEFAULT | ALL_GRAPHS}, + {BLT_CONFIG_COLOR, "-titlecolor", "titleColor", "Color", + DEF_AXIS_FOREGROUND, Blt_Offset(Axis, titleColor), + ALL_GRAPHS}, + {BLT_CONFIG_FONT, "-titlefont", "titleFont", "Font", DEF_AXIS_TITLE_FONT, + Blt_Offset(Axis, titleFont), ALL_GRAPHS}, + {BLT_CONFIG_CUSTOM, "-use", "use", "Use", (char *)NULL, 0, ALL_GRAPHS, + &useOption}, + {BLT_CONFIG_END, NULL, NULL, NULL, NULL, 0, 0} +}; + +/* Forward declarations */ +static void DestroyAxis(Axis *axisPtr); +static Tcl_FreeProc FreeAxis; +static int GetAxisByClass(Tcl_Interp *interp, Graph *graphPtr, Tcl_Obj *objPtr, + ClassId classId, Axis **axisPtrPtr); +static void TimeScaleAxis(Axis *axisPtr, double min, double max); + +static int lastMargin; +typedef int (GraphAxisProc)(Tcl_Interp *interp, Axis *axisPtr, int objc, + Tcl_Obj *const *objv); +typedef int (GraphVirtualAxisProc)(Tcl_Interp *interp, Graph *graphPtr, + int objc, Tcl_Obj *const *objv); + +INLINE static double +Clamp(double x) +{ + return (x < 0.0) ? 0.0 : (x > 1.0) ? 1.0 : x; +} + +INLINE static int +Round(double x) +{ + return (int) (x + ((x < 0.0) ? -0.5 : 0.5)); +} + +static void +SetAxisRange(AxisRange *rangePtr, double min, double max) +{ + rangePtr->min = min; + rangePtr->max = max; + rangePtr->range = max - min; + if (FABS(rangePtr->range) < DBL_EPSILON) { + rangePtr->range = 1.0; + } + rangePtr->scale = 1.0 / rangePtr->range; +} + +/* + *--------------------------------------------------------------------------- + * + * InRange -- + * + * Determines if a value lies within a given range. + * + * The value is normalized and compared against the interval [0..1], + * where 0.0 is the minimum and 1.0 is the maximum. DBL_EPSILON is the + * smallest number that can be represented on the host machine, such that + * (1.0 + epsilon) != 1.0. + * + * Please note, *max* can't equal *min*. + * + * Results: + * If the value is within the interval [min..max], 1 is returned; 0 + * otherwise. + * + *--------------------------------------------------------------------------- + */ +INLINE static int +InRange(double x, AxisRange *rangePtr) +{ + if (rangePtr->range < DBL_EPSILON) { + return (FABS(rangePtr->max - x) >= DBL_EPSILON); + } else { + double norm; + + norm = (x - rangePtr->min) * rangePtr->scale; + return ((norm >= -DBL_EPSILON) && ((norm - 1.0) < DBL_EPSILON)); + } +} + +INLINE static int +AxisIsHorizontal(Axis *axisPtr) +{ + Graph *graphPtr = axisPtr->obj.graphPtr; + + return ((axisPtr->obj.classId == CID_AXIS_Y) == graphPtr->inverted); +} + + +static void +ReleaseAxis(Axis *axisPtr) +{ + if (axisPtr != NULL) { + axisPtr->refCount--; + assert(axisPtr->refCount >= 0); + if (axisPtr->refCount == 0) { + axisPtr->flags |= DELETE_PENDING; + Tcl_EventuallyFree(axisPtr, FreeAxis); + } + } +} + +/* + *----------------------------------------------------------------------------- + * Custom option parse and print procedures + *----------------------------------------------------------------------------- + */ + +/*ARGSUSED*/ +static void +FreeAxisProc( + ClientData clientData, /* Not used. */ + Display *display, /* Not used. */ + char *widgRec, + int offset) +{ + Axis **axisPtrPtr = (Axis **)(widgRec + offset); + + if (*axisPtrPtr != NULL) { + ReleaseAxis(*axisPtrPtr); + *axisPtrPtr = NULL; + } +} + +/* + *--------------------------------------------------------------------------- + * + * ObjToAxisProc -- + * + * Converts the name of an axis to a pointer to its axis structure. + * + * Results: + * The return value is a standard TCL result. The axis flags are written + * into the widget record. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +ObjToAxisProc( + ClientData clientData, /* Class identifier of the type of + * axis we are looking for. */ + Tcl_Interp *interp, /* Interpreter to report results. */ + Tk_Window tkwin, /* Used to look up pointer to graph. */ + Tcl_Obj *objPtr, /* String representing new value. */ + char *widgRec, /* Pointer to structure record. */ + int offset, /* Offset to field in structure */ + int flags) +{ + ClassId classId = (ClassId)clientData; + Axis **axisPtrPtr = (Axis **)(widgRec + offset); + Axis *axisPtr; + Graph *graphPtr; + + if (flags & BLT_CONFIG_NULL_OK) { + const char *string; + + string = Tcl_GetString(objPtr); + if (string[0] == '\0') { + ReleaseAxis(*axisPtrPtr); + *axisPtrPtr = NULL; + return TCL_OK; + } + } + graphPtr = Blt_GetGraphFromWindowData(tkwin); + assert(graphPtr); + if (GetAxisByClass(interp, graphPtr, objPtr, classId, &axisPtr) + != TCL_OK) { + return TCL_ERROR; + } + ReleaseAxis(*axisPtrPtr); + *axisPtrPtr = axisPtr; + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * AxisToObjProc -- + * + * Convert the window coordinates into a string. + * + * Results: + * The string representing the coordinate position is returned. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static Tcl_Obj * +AxisToObjProc( + ClientData clientData, /* Not used. */ + Tcl_Interp *interp, /* Not used. */ + Tk_Window tkwin, /* Not used. */ + char *widgRec, /* Pointer to structure record .*/ + int offset, /* Offset to field in structure */ + int flags) /* Not used. */ +{ + Axis *axisPtr = *(Axis **)(widgRec + offset); + const char *name; + + name = (axisPtr == NULL) ? "" : axisPtr->obj.name; + return Tcl_NewStringObj(name, -1); +} + +/*ARGSUSED*/ +static void +FreeFormatProc( + ClientData clientData, /* Not used. */ + Display *display, /* Not used. */ + char *widgRec, + int offset) /* Not used. */ +{ + Axis *axisPtr = (Axis *)(widgRec); + + if (axisPtr->limitsFormats != NULL) { + Blt_Free(axisPtr->limitsFormats); + axisPtr->limitsFormats = NULL; + } + axisPtr->nFormats = 0; +} + + +/* + *--------------------------------------------------------------------------- + * + * ObjToFormatProc -- + * + * Convert the name of virtual axis to an pointer. + * + * Results: + * The return value is a standard TCL result. The axis flags are written + * into the widget record. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +ObjToFormatProc( + ClientData clientData, /* Not used. */ + Tcl_Interp *interp, /* Interpreter to report results. */ + Tk_Window tkwin, /* Not used. */ + Tcl_Obj *objPtr, /* String representing new value. */ + char *widgRec, /* Pointer to structure record. */ + int offset, /* Not used. */ + int flags) /* Not used. */ +{ + Axis *axisPtr = (Axis *)(widgRec); + const char **argv; + int argc; + + if (Tcl_SplitList(interp, Tcl_GetString(objPtr), &argc, &argv) != TCL_OK) { + return TCL_ERROR; + } + if (argc > 2) { + Tcl_AppendResult(interp, "too many elements in limits format list \"", + Tcl_GetString(objPtr), "\"", (char *)NULL); + Blt_Free(argv); + return TCL_ERROR; + } + if (axisPtr->limitsFormats != NULL) { + Blt_Free(axisPtr->limitsFormats); + } + axisPtr->limitsFormats = argv; + axisPtr->nFormats = argc; + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * FormatToObjProc -- + * + * Convert the window coordinates into a string. + * + * Results: + * The string representing the coordinate position is returned. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static Tcl_Obj * +FormatToObjProc( + ClientData clientData, /* Not used. */ + Tcl_Interp *interp, /* Not used. */ + Tk_Window tkwin, /* Not used. */ + char *widgRec, /* Widget record */ + int offset, /* Not used. */ + int flags) /* Not used. */ +{ + Axis *axisPtr = (Axis *)(widgRec); + Tcl_Obj *objPtr; + + if (axisPtr->nFormats == 0) { + objPtr = Tcl_NewStringObj("", -1); + } else { + const char *string; + + string = Tcl_Merge(axisPtr->nFormats, axisPtr->limitsFormats); + objPtr = Tcl_NewStringObj(string, -1); + Blt_Free(string); + } + return objPtr; +} + +/* + *--------------------------------------------------------------------------- + * + * ObjToLimitProc -- + * + * Convert the string representation of an axis limit into its numeric + * form. + * + * Results: + * The return value is a standard TCL result. The symbol type is written + * into the widget record. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +ObjToLimitProc( + ClientData clientData, /* Not used. */ + Tcl_Interp *interp, /* Interpreter to send results back + * to */ + Tk_Window tkwin, /* Not used. */ + Tcl_Obj *objPtr, /* String representing new value. */ + char *widgRec, /* Pointer to structure record. */ + int offset, /* Offset to field in structure */ + int flags) /* Not used. */ +{ + double *limitPtr = (double *)(widgRec + offset); + const char *string; + + string = Tcl_GetString(objPtr); + if (string[0] == '\0') { + *limitPtr = Blt_NaN(); + } else if (Blt_ExprDoubleFromObj(interp, objPtr, limitPtr) != TCL_OK) { + return TCL_ERROR; + } + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * LimitToObjProc -- + * + * Convert the floating point axis limits into a string. + * + * Results: + * The string representation of the limits is returned. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static Tcl_Obj * +LimitToObjProc( + ClientData clientData, /* Not used. */ + Tcl_Interp *interp, /* Not used. */ + Tk_Window tkwin, /* Not used. */ + char *widgRec, /* */ + int offset, /* Offset to field in structure */ + int flags) /* Not used. */ +{ + double limit = *(double *)(widgRec + offset); + Tcl_Obj *objPtr; + + if (DEFINED(limit)) { + objPtr = Tcl_NewDoubleObj(limit); + } else { + objPtr = Tcl_NewStringObj("", -1); + } + return objPtr; +} + +/* + *--------------------------------------------------------------------------- + * + * ObjToUseProc -- + * + * Convert the string representation of the margin to use into its + * numeric form. + * + * Results: + * The return value is a standard TCL result. The use type is written + * into the widget record. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +ObjToUseProc( + ClientData clientData, /* Not used. */ + Tcl_Interp *interp, /* Interpreter to send results. */ + Tk_Window tkwin, /* Not used. */ + Tcl_Obj *objPtr, /* String representing new value. */ + char *widgRec, /* Pointer to structure record. */ + int offset, /* Offset to field in structure */ + int flags) /* Not used. */ +{ + Axis *axisPtr = (Axis *)(widgRec); + AxisName *p, *pend; + Blt_Chain chain; + Graph *graphPtr; + const char *string; + int margin; + + graphPtr = axisPtr->obj.graphPtr; + if (axisPtr->refCount == 0) { + /* Clear the axis class if it's not currently used by an element.*/ + Blt_GraphSetObjectClass(&axisPtr->obj, CID_NONE); + } + /* Remove the axis from the margin's use list and clear its use flag. */ + if (axisPtr->link != NULL) { + Blt_Chain_UnlinkLink(axisPtr->chain, axisPtr->link); + } + axisPtr->flags &= ~AXIS_USE; + string = Tcl_GetString(objPtr); + if ((string == NULL) || (string[0] == '\0')) { + goto done; + } + for (p = axisNames, pend = axisNames + nAxisNames; p < pend; p++) { + if (strcmp(p->name, string) == 0) { + break; /* Found the axis name. */ + } + } + if (p == pend) { + Tcl_AppendResult(interp, "unknown axis type \"", string, "\": " + "should be x, y, x1, y2, or \"\".", (char *)NULL); + return TCL_ERROR; + } + /* Check the axis class. Can't use the axis if it's already being used as + * another type. */ + if (axisPtr->obj.classId == CID_NONE) { + Blt_GraphSetObjectClass(&axisPtr->obj, p->classId); + } else if (axisPtr->obj.classId != p->classId) { + Tcl_AppendResult(interp, "wrong type for axis \"", + axisPtr->obj.name, "\": can't use ", + axisPtr->obj.className, " type axis.", (char *)NULL); + return TCL_ERROR; + } + margin = (graphPtr->inverted) ? p->invertMargin : p->margin; + chain = graphPtr->margins[margin].axes; + if (axisPtr->link != NULL) { + /* Move the axis from the old margin's "use" list to the new. */ + Blt_Chain_AppendLink(chain, axisPtr->link); + } else { + axisPtr->link = Blt_Chain_Append(chain, axisPtr); + } + axisPtr->chain = chain; + axisPtr->flags |= AXIS_USE; + axisPtr->margin = margin; + done: + graphPtr->flags |= (GET_AXIS_GEOMETRY | LAYOUT_NEEDED | RESET_AXES); + /* When any axis changes, we need to layout the entire graph. */ + graphPtr->flags |= (MAP_WORLD | REDRAW_WORLD); + Blt_EventuallyRedrawGraph(graphPtr); + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * UseToObjProc -- + * + * Convert the floating point axis limits into a string. + * + * Results: + * The string representation of the limits is returned. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static Tcl_Obj * +UseToObjProc( + ClientData clientData, /* Not used. */ + Tcl_Interp *interp, /* Not used. */ + Tk_Window tkwin, /* Not used. */ + char *widgRec, /* */ + int offset, /* Offset to field in structure */ + int flags) /* Not used. */ +{ + Axis *axisPtr = (Axis *)(widgRec); + + if (axisPtr->margin == MARGIN_NONE) { + return Tcl_NewStringObj("", -1); + } + return Tcl_NewStringObj(axisNames[axisPtr->margin].name, -1); +} + +/*ARGSUSED*/ +static void +FreeTicksProc( + ClientData clientData, /* Either AXIS_AUTO_MAJOR or + * AXIS_AUTO_MINOR. */ + Display *display, /* Not used. */ + char *widgRec, + int offset) +{ + Axis *axisPtr = (Axis *)widgRec; + Ticks **ticksPtrPtr = (Ticks **) (widgRec + offset); + unsigned long mask = (unsigned long)clientData; + + axisPtr->flags |= mask; + if (*ticksPtrPtr != NULL) { + Blt_Free(*ticksPtrPtr); + } + *ticksPtrPtr = NULL; +} + +/* + *--------------------------------------------------------------------------- + * + * ObjToTicksProc -- + * + * + * Results: + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +ObjToTicksProc( + ClientData clientData, /* Either AXIS_AUTO_MAJOR or + * AXIS_AUTO_MINOR. */ + Tcl_Interp *interp, /* Interpreter to send results. */ + Tk_Window tkwin, /* Not used. */ + Tcl_Obj *objPtr, /* String representing new value. */ + char *widgRec, /* Pointer to structure record. */ + int offset, /* Offset to field in structure */ + int flags) /* Not used. */ +{ + Axis *axisPtr = (Axis *)widgRec; + Tcl_Obj **objv; + Ticks **ticksPtrPtr = (Ticks **) (widgRec + offset); + Ticks *ticksPtr; + int objc; + unsigned long mask = (unsigned long)clientData; + + if (Tcl_ListObjGetElements(interp, objPtr, &objc, &objv) != TCL_OK) { + return TCL_ERROR; + } + axisPtr->flags |= mask; + ticksPtr = NULL; + if (objc > 0) { + int i; + + ticksPtr = Blt_AssertMalloc(sizeof(Ticks) + (objc*sizeof(double))); + for (i = 0; i < objc; i++) { + double value; + + if (Blt_ExprDoubleFromObj(interp, objv[i], &value) != TCL_OK) { + Blt_Free(ticksPtr); + return TCL_ERROR; + } + ticksPtr->values[i] = value; + } + ticksPtr->nTicks = objc; + axisPtr->flags &= ~mask; + } + FreeTicksProc(clientData, Tk_Display(tkwin), widgRec, offset); + *ticksPtrPtr = ticksPtr; + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * TicksToObjProc -- + * + * Convert array of tick coordinates to a list. + * + * Results: + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static Tcl_Obj * +TicksToObjProc( + ClientData clientData, /* Either AXIS_AUTO_MAJOR or + * AXIS_AUTO_MINOR. */ + Tcl_Interp *interp, + Tk_Window tkwin, /* Not used. */ + char *widgRec, /* */ + int offset, /* Offset to field in structure */ + int flags) /* Not used. */ +{ + Axis *axisPtr; + Tcl_Obj *listObjPtr; + Ticks *ticksPtr; + unsigned long mask; + + axisPtr = (Axis *)widgRec; + ticksPtr = *(Ticks **) (widgRec + offset); + mask = (unsigned long)clientData; + listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL); + if ((ticksPtr != NULL) && ((axisPtr->flags & mask) == 0)) { + unsigned int i; + + for (i = 0; i < ticksPtr->nTicks; i++) { + Tcl_Obj *objPtr; + + objPtr = Tcl_NewDoubleObj(ticksPtr->values[i]); + Tcl_ListObjAppendElement(interp, listObjPtr, objPtr); + } + } + return listObjPtr; +} + +/* + *--------------------------------------------------------------------------- + * + * ObjToLooseProc -- + * + * Convert a string to one of three values. + * 0 - false, no, off + * 1 - true, yes, on + * 2 - always + * Results: + * If the string is successfully converted, TCL_OK is returned. + * Otherwise, TCL_ERROR is returned and an error message is left in + * interpreter's result field. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +ObjToLooseProc( + ClientData clientData, /* Not used. */ + Tcl_Interp *interp, /* Interpreter to send results. */ + Tk_Window tkwin, /* Not used. */ + Tcl_Obj *objPtr, /* String representing new value. */ + char *widgRec, /* Pointer to structure record. */ + int offset, /* Not used. */ + int flags) /* Not used. */ +{ + Axis *axisPtr = (Axis *)(widgRec); + Tcl_Obj **objv; + int i; + int objc; + int values[2]; + + if (Tcl_ListObjGetElements(interp, objPtr, &objc, &objv) != TCL_OK) { + return TCL_ERROR; + } + if ((objc < 1) || (objc > 2)) { + Tcl_AppendResult(interp, "wrong # elements in loose value \"", + Tcl_GetString(objPtr), "\"", (char *)NULL); + return TCL_ERROR; + } + for (i = 0; i < objc; i++) { + const char *string; + + string = Tcl_GetString(objv[i]); + if ((string[0] == 'a') && (strcmp(string, "always") == 0)) { + values[i] = AXIS_ALWAYS_LOOSE; + } else { + int bool; + + if (Tcl_GetBooleanFromObj(interp, objv[i], &bool) != TCL_OK) { + return TCL_ERROR; + } + values[i] = bool; + } + } + axisPtr->looseMin = axisPtr->looseMax = values[0]; + if (objc > 1) { + axisPtr->looseMax = values[1]; + } + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * LooseToObjProc -- + * + * Results: + * The string representation of the auto boolean is returned. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static Tcl_Obj * +LooseToObjProc( + ClientData clientData, /* Not used. */ + Tcl_Interp *interp, + Tk_Window tkwin, /* Not used. */ + char *widgRec, /* Widget record */ + int offset, /* Not used. */ + int flags) /* Not used. */ +{ + Axis *axisPtr = (Axis *)widgRec; + Tcl_Obj *listObjPtr; + + listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL); + if (axisPtr->looseMin == AXIS_TIGHT) { + Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewBooleanObj(FALSE)); + } else if (axisPtr->looseMin == AXIS_LOOSE) { + Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewBooleanObj(TRUE)); + } else if (axisPtr->looseMin == AXIS_ALWAYS_LOOSE) { + Tcl_ListObjAppendElement(interp, listObjPtr, + Tcl_NewStringObj("always", 6)); + } + if (axisPtr->looseMin != axisPtr->looseMax) { + if (axisPtr->looseMax == AXIS_TIGHT) { + Tcl_ListObjAppendElement(interp, listObjPtr, + Tcl_NewBooleanObj(FALSE)); + } else if (axisPtr->looseMax == AXIS_LOOSE) { + Tcl_ListObjAppendElement(interp, listObjPtr, + Tcl_NewBooleanObj(TRUE)); + } else if (axisPtr->looseMax == AXIS_ALWAYS_LOOSE) { + Tcl_ListObjAppendElement(interp, listObjPtr, + Tcl_NewStringObj("always", 6)); + } + } + return listObjPtr; +} + +static void +FreeTickLabels(Blt_Chain chain) +{ + Blt_ChainLink link; + + for (link = Blt_Chain_FirstLink(chain); link != NULL; + link = Blt_Chain_NextLink(link)) { + TickLabel *labelPtr; + + labelPtr = Blt_Chain_GetValue(link); + Blt_Free(labelPtr); + } + Blt_Chain_Reset(chain); +} + +/* + *--------------------------------------------------------------------------- + * + * MakeLabel -- + * + * Converts a floating point tick value to a string to be used as its + * label. + * + * Results: + * None. + * + * Side Effects: + * Returns a new label in the string character buffer. The formatted + * tick label will be displayed on the graph. + * + * -------------------------------------------------------------------------- + */ +static TickLabel * +MakeLabel(Axis *axisPtr, double value) +{ +#define TICK_LABEL_SIZE 200 + char string[TICK_LABEL_SIZE + 1]; + TickLabel *labelPtr; + + /* Generate a default tick label based upon the tick value. */ + if (axisPtr->logScale) { + sprintf_s(string, TICK_LABEL_SIZE, "1E%d", ROUND(value)); + } else { + sprintf_s(string, TICK_LABEL_SIZE, "%.*G", NUMDIGITS, value); + } + + if (axisPtr->formatCmd != NULL) { + Graph *graphPtr; + Tcl_Interp *interp; + Tk_Window tkwin; + + graphPtr = axisPtr->obj.graphPtr; + interp = graphPtr->interp; + tkwin = graphPtr->tkwin; + /* + * A TCL proc was designated to format tick labels. Append the path + * name of the widget and the default tick label as arguments when + * invoking it. Copy and save the new label from interp->result. + */ + Tcl_ResetResult(interp); + if (Tcl_VarEval(interp, axisPtr->formatCmd, " ", Tk_PathName(tkwin), + " ", string, (char *)NULL) != TCL_OK) { + Tcl_BackgroundError(interp); + } else { + /* + * The proc could return a string of any length, so arbitrarily + * limit it to what will fit in the return string. + */ + strncpy(string, Tcl_GetStringResult(interp), TICK_LABEL_SIZE); + string[TICK_LABEL_SIZE] = '\0'; + + Tcl_ResetResult(interp); /* Clear the interpreter's result. */ + } + } + labelPtr = Blt_AssertMalloc(sizeof(TickLabel) + strlen(string)); + strcpy(labelPtr->string, string); + labelPtr->anchorPos.x = labelPtr->anchorPos.y = DBL_MAX; + return labelPtr; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_InvHMap -- + * + * Maps the given screen coordinate back to a graph coordinate. Called + * by the graph locater routine. + * + * Results: + * Returns the graph coordinate value at the given window + * y-coordinate. + * + *--------------------------------------------------------------------------- + */ +double +Blt_InvHMap(Axis *axisPtr, double x) +{ + double value; + + x = (double)(x - axisPtr->screenMin) * axisPtr->screenScale; + if (axisPtr->descending) { + x = 1.0 - x; + } + value = (x * axisPtr->axisRange.range) + axisPtr->axisRange.min; + if (axisPtr->logScale) { + value = EXP10(value); + } + return value; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_InvVMap -- + * + * Maps the given screen y-coordinate back to the graph coordinate + * value. Called by the graph locater routine. + * + * Results: + * Returns the graph coordinate value for the given screen + * coordinate. + * + *--------------------------------------------------------------------------- + */ +double +Blt_InvVMap(Axis *axisPtr, double y) /* Screen coordinate */ +{ + double value; + + y = (double)(y - axisPtr->screenMin) * axisPtr->screenScale; + if (axisPtr->descending) { + y = 1.0 - y; + } + value = ((1.0 - y) * axisPtr->axisRange.range) + axisPtr->axisRange.min; + if (axisPtr->logScale) { + value = EXP10(value); + } + return value; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_HMap -- + * + * Map the given graph coordinate value to its axis, returning a window + * position. + * + * Results: + * Returns a double precision number representing the window coordinate + * position on the given axis. + * + *--------------------------------------------------------------------------- + */ +double +Blt_HMap(Axis *axisPtr, double x) +{ + if ((axisPtr->logScale) && (x != 0.0)) { + x = log10(FABS(x)); + } + /* Map graph coordinate to normalized coordinates [0..1] */ + x = (x - axisPtr->axisRange.min) * axisPtr->axisRange.scale; + if (axisPtr->descending) { + x = 1.0 - x; + } + return (x * axisPtr->screenRange + axisPtr->screenMin); +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_VMap -- + * + * Map the given graph coordinate value to its axis, returning a window + * position. + * + * Results: + * Returns a double precision number representing the window coordinate + * position on the given axis. + * + *--------------------------------------------------------------------------- + */ +double +Blt_VMap(Axis *axisPtr, double y) +{ + if ((axisPtr->logScale) && (y != 0.0)) { + y = log10(FABS(y)); + } + /* Map graph coordinate to normalized coordinates [0..1] */ + y = (y - axisPtr->axisRange.min) * axisPtr->axisRange.scale; + if (axisPtr->descending) { + y = 1.0 - y; + } + return ((1.0 - y) * axisPtr->screenRange + axisPtr->screenMin); +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_Map2D -- + * + * Maps the given graph x,y coordinate values to a window position. + * + * Results: + * Returns a XPoint structure containing the window coordinates of + * the given graph x,y coordinate. + * + *--------------------------------------------------------------------------- + */ +Point2d +Blt_Map2D( + Graph *graphPtr, + double x, double y, /* Graph x and y coordinates */ + Axis2d *axesPtr) /* Specifies which axes to use */ +{ + Point2d point; + + if (graphPtr->inverted) { + point.x = Blt_HMap(axesPtr->y, y); + point.y = Blt_VMap(axesPtr->x, x); + } else { + point.x = Blt_HMap(axesPtr->x, x); + point.y = Blt_VMap(axesPtr->y, y); + } + return point; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_InvMap2D -- + * + * Maps the given window x,y coordinates to graph values. + * + * Results: + * Returns a structure containing the graph coordinates of the given + * window x,y coordinate. + * + *--------------------------------------------------------------------------- + */ +Point2d +Blt_InvMap2D( + Graph *graphPtr, + double x, double y, /* Window x and y coordinates */ + Axis2d *axesPtr) /* Specifies which axes to use */ +{ + Point2d point; + + if (graphPtr->inverted) { + point.x = Blt_InvVMap(axesPtr->x, y); + point.y = Blt_InvHMap(axesPtr->y, x); + } else { + point.x = Blt_InvHMap(axesPtr->x, x); + point.y = Blt_InvVMap(axesPtr->y, y); + } + return point; +} + + +static void +GetDataLimits(Axis *axisPtr, double min, double max) +{ + if (axisPtr->valueRange.min > min) { + axisPtr->valueRange.min = min; + } + if (axisPtr->valueRange.max < max) { + axisPtr->valueRange.max = max; + } +} + +static void +FixAxisRange(Axis *axisPtr) +{ + double min, max; + + /* + * When auto-scaling, the axis limits are the bounds of the element data. + * If no data exists, set arbitrary limits (wrt to log/linear scale). + */ + min = axisPtr->valueRange.min; + max = axisPtr->valueRange.max; + + /* Check the requested axis limits. Can't allow -min to be greater + * than -max, or have undefined log scale limits. */ + if (((DEFINED(axisPtr->reqMin)) && (DEFINED(axisPtr->reqMax))) && + (axisPtr->reqMin >= axisPtr->reqMax)) { + axisPtr->reqMin = axisPtr->reqMax = Blt_NaN(); + } + if (axisPtr->logScale) { + if ((DEFINED(axisPtr->reqMin)) && (axisPtr->reqMin <= 0.0)) { + axisPtr->reqMin = Blt_NaN(); + } + if ((DEFINED(axisPtr->reqMax)) && (axisPtr->reqMax <= 0.0)) { + axisPtr->reqMax = Blt_NaN(); + } + } + + if (min == DBL_MAX) { + if (DEFINED(axisPtr->reqMin)) { + min = axisPtr->reqMin; + } else { + min = (axisPtr->logScale) ? 0.001 : 0.0; + } + } + if (max == -DBL_MAX) { + if (DEFINED(axisPtr->reqMax)) { + max = axisPtr->reqMax; + } else { + max = 1.0; + } + } + if (min >= max) { + /* + * There is no range of data (i.e. min is not less than max), so + * manufacture one. + */ + if (min == 0.0) { + min = 0.0, max = 1.0; + } else { + max = min + (FABS(min) * 0.1); + } + } + SetAxisRange(&axisPtr->valueRange, min, max); + + /* + * The axis limits are either the current data range or overridden by the + * values selected by the user with the -min or -max options. + */ + axisPtr->min = min; + axisPtr->max = max; + if (DEFINED(axisPtr->reqMin)) { + axisPtr->min = axisPtr->reqMin; + } + if (DEFINED(axisPtr->reqMax)) { + axisPtr->max = axisPtr->reqMax; + } + if (axisPtr->max < axisPtr->min) { + /* + * If the limits still don't make sense, it's because one limit + * configuration option (-min or -max) was set and the other default + * (based upon the data) is too small or large. Remedy this by making + * up a new min or max from the user-defined limit. + */ + if (!DEFINED(axisPtr->reqMin)) { + axisPtr->min = axisPtr->max - (FABS(axisPtr->max) * 0.1); + } + if (!DEFINED(axisPtr->reqMax)) { + axisPtr->max = axisPtr->min + (FABS(axisPtr->max) * 0.1); + } + } + /* + * If a window size is defined, handle auto ranging by shifting the axis + * limits. + */ + if ((axisPtr->windowSize > 0.0) && + (!DEFINED(axisPtr->reqMin)) && (!DEFINED(axisPtr->reqMax))) { + if (axisPtr->shiftBy < 0.0) { + axisPtr->shiftBy = 0.0; + } + max = axisPtr->min + axisPtr->windowSize; + if (axisPtr->max >= max) { + if (axisPtr->shiftBy > 0.0) { + max = UCEIL(axisPtr->max, axisPtr->shiftBy); + } + axisPtr->min = max - axisPtr->windowSize; + } + axisPtr->max = max; + } + if ((axisPtr->max != axisPtr->prevMax) || + (axisPtr->min != axisPtr->prevMin)) { + /* Indicate if the axis limits have changed */ + axisPtr->flags |= DIRTY; + /* and save the previous minimum and maximum values */ + axisPtr->prevMin = axisPtr->min; + axisPtr->prevMax = axisPtr->max; + } +} + +/* + *--------------------------------------------------------------------------- + * + * NiceNum -- + * + * Reference: Paul Heckbert, "Nice Numbers for Graph Labels", + * Graphics Gems, pp 61-63. + * + * Finds a "nice" number approximately equal to x. + * + *--------------------------------------------------------------------------- + */ +static double +NiceNum(double x, int round) /* If non-zero, round. Otherwise take + * ceiling of value. */ +{ + double expt; /* Exponent of x */ + double frac; /* Fractional part of x */ + double nice; /* Nice, rounded fraction */ + + expt = floor(log10(x)); + frac = x / EXP10(expt); /* between 1 and 10 */ + if (round) { + if (frac < 1.5) { + nice = 1.0; + } else if (frac < 3.0) { + nice = 2.0; + } else if (frac < 7.0) { + nice = 5.0; + } else { + nice = 10.0; + } + } else { + if (frac <= 1.0) { + nice = 1.0; + } else if (frac <= 2.0) { + nice = 2.0; + } else if (frac <= 5.0) { + nice = 5.0; + } else { + nice = 10.0; + } + } + return nice * EXP10(expt); +} + +static Ticks * +GenerateTicks(TickSweep *sweepPtr) +{ + Ticks *ticksPtr; + + ticksPtr = Blt_AssertMalloc(sizeof(Ticks) + + (sweepPtr->nSteps * sizeof(double))); + ticksPtr->nTicks = 0; + + if (sweepPtr->step == 0.0) { + /* Hack: A zero step indicates to use log values. */ + int i; + /* Precomputed log10 values [1..10] */ + static double logTable[] = { + 0.0, + 0.301029995663981, + 0.477121254719662, + 0.602059991327962, + 0.698970004336019, + 0.778151250383644, + 0.845098040014257, + 0.903089986991944, + 0.954242509439325, + 1.0 + }; + for (i = 0; i < sweepPtr->nSteps; i++) { + ticksPtr->values[i] = logTable[i]; + } + } else { + double value; + int i; + + value = sweepPtr->initial; /* Start from smallest axis tick */ + for (i = 0; i < sweepPtr->nSteps; i++) { + value = UROUND(value, sweepPtr->step); + ticksPtr->values[i] = value; + value += sweepPtr->step; + } + } + ticksPtr->nTicks = sweepPtr->nSteps; + return ticksPtr; +} + +/* + *--------------------------------------------------------------------------- + * + * LogScaleAxis -- + * + * Determine the range and units of a log scaled axis. + * + * Unless the axis limits are specified, the axis is scaled + * automatically, where the smallest and largest major ticks encompass + * the range of actual data values. When an axis limit is specified, + * that value represents the smallest(min)/largest(max) value in the + * displayed range of values. + * + * Both manual and automatic scaling are affected by the step used. By + * default, the step is the largest power of ten to divide the range in + * more than one piece. + * + * Automatic scaling: + * Find the smallest number of units which contain the range of values. + * The minimum and maximum major tick values will be represent the + * range of values for the axis. This greatest number of major ticks + * possible is 10. + * + * Manual scaling: + * Make the minimum and maximum data values the represent the range of + * the values for the axis. The minimum and maximum major ticks will be + * inclusive of this range. This provides the largest area for plotting + * and the expected results when the axis min and max values have be set + * by the user (.e.g zooming). The maximum number of major ticks is 20. + * + * For log scale, there's the possibility that the minimum and + * maximum data values are the same magnitude. To represent the + * points properly, at least one full decade should be shown. + * However, if you zoom a log scale plot, the results should be + * predictable. Therefore, in that case, show only minor ticks. + * Lastly, there should be an appropriate way to handle numbers + * <=0. + * + * maxY + * | units = magnitude (of least significant digit) + * | high = largest unit tick < max axis value + * high _| low = smallest unit tick > min axis value + * | + * | range = high - low + * | # ticks = greatest factor of range/units + * _| + * U | + * n | + * i | + * t _| + * | + * | + * | + * low _| + * | + * |_minX________________maxX__ + * | | | | | + * minY low high + * minY + * + * + * numTicks = Number of ticks + * min = Minimum value of axis + * max = Maximum value of axis + * range = Range of values (max - min) + * + * If the number of decades is greater than ten, it is assumed + * that the full set of log-style ticks can't be drawn properly. + * + * Results: + * None + * + * -------------------------------------------------------------------------- */ +static void +LogScaleAxis(Axis *axisPtr, double min, double max) +{ + double range; + double tickMin, tickMax; + double majorStep, minorStep; + int nMajor, nMinor; + + nMajor = nMinor = 0; + /* Suppress compiler warnings. */ + majorStep = minorStep = 0.0; + tickMin = tickMax = Blt_NaN(); + if (min < max) { + min = (min != 0.0) ? log10(FABS(min)) : 0.0; + max = (max != 0.0) ? log10(FABS(max)) : 1.0; + + tickMin = floor(min); + tickMax = ceil(max); + range = tickMax - tickMin; + + if (range > 10) { + /* There are too many decades to display a major tick at every + * decade. Instead, treat the axis as a linear scale. */ + range = NiceNum(range, 0); + majorStep = NiceNum(range / axisPtr->reqNumMajorTicks, 1); + tickMin = UFLOOR(tickMin, majorStep); + tickMax = UCEIL(tickMax, majorStep); + nMajor = (int)((tickMax - tickMin) / majorStep) + 1; + minorStep = EXP10(floor(log10(majorStep))); + if (minorStep == majorStep) { + nMinor = 4, minorStep = 0.2; + } else { + nMinor = Round(majorStep / minorStep) - 1; + } + } else { + if (tickMin == tickMax) { + tickMax++; + } + majorStep = 1.0; + nMajor = (int)(tickMax - tickMin + 1); /* FIXME: Check this. */ + + minorStep = 0.0; /* This is a special hack to pass + * information to the GenerateTicks + * routine. An interval of 0.0 tells 1) + * this is a minor sweep and 2) the axis + * is log scale. */ + nMinor = 10; + } + if ((axisPtr->looseMin == AXIS_TIGHT) || + ((axisPtr->looseMin == AXIS_LOOSE) && + (DEFINED(axisPtr->reqMin)))) { + tickMin = min; + nMajor++; + } + if ((axisPtr->looseMax == AXIS_TIGHT) || + ((axisPtr->looseMax == AXIS_LOOSE) && + (DEFINED(axisPtr->reqMax)))) { + tickMax = max; + } + } + axisPtr->majorSweep.step = majorStep; + axisPtr->majorSweep.initial = floor(tickMin); + axisPtr->majorSweep.nSteps = nMajor; + axisPtr->minorSweep.initial = axisPtr->minorSweep.step = minorStep; + axisPtr->minorSweep.nSteps = nMinor; + + SetAxisRange(&axisPtr->axisRange, tickMin, tickMax); +} + +/* + *--------------------------------------------------------------------------- + * + * LinearScaleAxis -- + * + * Determine the units of a linear scaled axis. + * + * The axis limits are either the range of the data values mapped + * to the axis (autoscaled), or the values specified by the -min + * and -max options (manual). + * + * If autoscaled, the smallest and largest major ticks will + * encompass the range of data values. If the -loose option is + * selected, the next outer ticks are choosen. If tight, the + * ticks are at or inside of the data limits are used. + * + * If manually set, the ticks are at or inside the data limits + * are used. This makes sense for zooming. You want the + * selected range to represent the next limit, not something a + * bit bigger. + * + * Note: I added an "always" value to the -loose option to force + * the manually selected axes to be loose. It's probably + * not a good idea. + * + * maxY + * | units = magnitude (of least significant digit) + * | high = largest unit tick < max axis value + * high _| low = smallest unit tick > min axis value + * | + * | range = high - low + * | # ticks = greatest factor of range/units + * _| + * U | + * n | + * i | + * t _| + * | + * | + * | + * low _| + * | + * |_minX________________maxX__ + * | | | | | + * minY low high + * minY + * + * numTicks = Number of ticks + * min = Minimum value of axis + * max = Maximum value of axis + * range = Range of values (max - min) + * + * Results: + * None. + * + * Side Effects: + * The axis tick information is set. The actual tick values will + * be generated later. + * + *--------------------------------------------------------------------------- + */ +static void +LinearScaleAxis(Axis *axisPtr, double min, double max) +{ + double step; + double tickMin, tickMax; + double axisMin, axisMax; + unsigned int nTicks; + + nTicks = 0; + step = 1.0; + /* Suppress compiler warning. */ + axisMin = axisMax = tickMin = tickMax = Blt_NaN(); + if (min < max) { + double range; + + range = max - min; + /* Calculate the major tick stepping. */ + if (axisPtr->reqStep > 0.0) { + /* An interval was designated by the user. Keep scaling it until + * it fits comfortably within the current range of the axis. */ + step = axisPtr->reqStep; + while ((2 * step) >= range) { + step *= 0.5; + } + } else { + range = NiceNum(range, 0); + step = NiceNum(range / axisPtr->reqNumMajorTicks, 1); + } + + /* Find the outer tick values. Add 0.0 to prevent getting -0.0. */ + axisMin = tickMin = floor(min / step) * step + 0.0; + axisMax = tickMax = ceil(max / step) * step + 0.0; + + nTicks = Round((tickMax - tickMin) / step) + 1; + } + axisPtr->majorSweep.step = step; + axisPtr->majorSweep.initial = tickMin; + axisPtr->majorSweep.nSteps = nTicks; + + /* + * The limits of the axis are either the range of the data ("tight") or at + * the next outer tick interval ("loose"). The looseness or tightness has + * to do with how the axis fits the range of data values. This option is + * overridden when the user sets an axis limit (by either -min or -max + * option). The axis limit is always at the selected limit (otherwise we + * assume that user would have picked a different number). + */ + if ((axisPtr->looseMin == AXIS_TIGHT) || + ((axisPtr->looseMin == AXIS_LOOSE) && + (DEFINED(axisPtr->reqMin)))) { + axisMin = min; + } + if ((axisPtr->looseMax == AXIS_TIGHT) || + ((axisPtr->looseMax == AXIS_LOOSE) && + (DEFINED(axisPtr->reqMax)))) { + axisMax = max; + } + SetAxisRange(&axisPtr->axisRange, axisMin, axisMax); + + /* Now calculate the minor tick step and number. */ + + if ((axisPtr->reqNumMinorTicks > 0) && (axisPtr->flags & AXIS_AUTO_MAJOR)) { + nTicks = axisPtr->reqNumMinorTicks - 1; + step = 1.0 / (nTicks + 1); + } else { + nTicks = 0; /* No minor ticks. */ + step = 0.5; /* Don't set the minor tick interval to + * 0.0. It makes the GenerateTicks + * routine * create minor log-scale tick + * marks. */ + } + axisPtr->minorSweep.initial = axisPtr->minorSweep.step = step; + axisPtr->minorSweep.nSteps = nTicks; +} + + +static void +SweepTicks(Axis *axisPtr) +{ + if (axisPtr->flags & AXIS_AUTO_MAJOR) { + if (axisPtr->t1Ptr != NULL) { + Blt_Free(axisPtr->t1Ptr); + } + axisPtr->t1Ptr = GenerateTicks(&axisPtr->majorSweep); + } + if (axisPtr->flags & AXIS_AUTO_MINOR) { + if (axisPtr->t2Ptr != NULL) { + Blt_Free(axisPtr->t2Ptr); + } + axisPtr->t2Ptr = GenerateTicks(&axisPtr->minorSweep); + } +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_ResetAxes -- + * + * Results: + * None. + * + *--------------------------------------------------------------------------- + */ +void +Blt_ResetAxes(Graph *graphPtr) +{ + Blt_ChainLink link; + Blt_HashEntry *hPtr; + Blt_HashSearch cursor; + + /* FIXME: This should be called whenever the display list of + * elements change. Maybe yet another flag INIT_STACKS to + * indicate that the element display list has changed. + * Needs to be done before the axis limits are set. + */ + Blt_InitBarSetTable(graphPtr); + if ((graphPtr->mode == BARS_STACKED) && (graphPtr->nBarGroups > 0)) { + Blt_ComputeBarStacks(graphPtr); + } + /* + * Step 1: Reset all axes. Initialize the data limits of the axis to + * impossible values. + */ + for (hPtr = Blt_FirstHashEntry(&graphPtr->axes.table, &cursor); + hPtr != NULL; hPtr = Blt_NextHashEntry(&cursor)) { + Axis *axisPtr; + + axisPtr = Blt_GetHashValue(hPtr); + axisPtr->min = axisPtr->valueRange.min = DBL_MAX; + axisPtr->max = axisPtr->valueRange.max = -DBL_MAX; + } + + /* + * Step 2: For each element that's to be displayed, get the smallest + * and largest data values mapped to each X and Y-axis. This + * will be the axis limits if the user doesn't override them + * with -min and -max options. + */ + for (link = Blt_Chain_FirstLink(graphPtr->elements.displayList); + link != NULL; link = Blt_Chain_NextLink(link)) { + Element *elemPtr; + Region2d exts; + + elemPtr = Blt_Chain_GetValue(link); + if ((graphPtr->flags & UNMAP_HIDDEN) && (elemPtr->flags & HIDE)) { + continue; + } + (*elemPtr->procsPtr->extentsProc) (elemPtr, &exts); + GetDataLimits(elemPtr->axes.x, exts.left, exts.right); + GetDataLimits(elemPtr->axes.y, exts.top, exts.bottom); + } + /* + * Step 3: Now that we know the range of data values for each axis, + * set axis limits and compute a sweep to generate tick values. + */ + for (hPtr = Blt_FirstHashEntry(&graphPtr->axes.table, &cursor); + hPtr != NULL; hPtr = Blt_NextHashEntry(&cursor)) { + Axis *axisPtr; + double min, max; + + axisPtr = Blt_GetHashValue(hPtr); + FixAxisRange(axisPtr); + + /* Calculate min/max tick (major/minor) layouts */ + min = axisPtr->min; + max = axisPtr->max; + if ((DEFINED(axisPtr->scrollMin)) && (min < axisPtr->scrollMin)) { + min = axisPtr->scrollMin; + } + if ((DEFINED(axisPtr->scrollMax)) && (max > axisPtr->scrollMax)) { + max = axisPtr->scrollMax; + } + if (axisPtr->logScale) { + LogScaleAxis(axisPtr, min, max); + } else if (axisPtr->timeScale) { + TimeScaleAxis(axisPtr, min, max); + } else { + LinearScaleAxis(axisPtr, min, max); + } + + if ((axisPtr->flags & (DIRTY|AXIS_USE)) == (DIRTY|AXIS_USE)) { + graphPtr->flags |= CACHE_DIRTY; + } + } + + graphPtr->flags &= ~RESET_AXES; + + /* + * When any axis changes, we need to layout the entire graph. + */ + graphPtr->flags |= (GET_AXIS_GEOMETRY | LAYOUT_NEEDED | MAP_ALL | + REDRAW_WORLD); +} + +/* + *--------------------------------------------------------------------------- + * + * ResetTextStyles -- + * + * Configures axis attributes (font, line width, label, etc) and + * allocates a new (possibly shared) graphics context. Line cap style is + * projecting. This is for the problem of when a tick sits directly at + * the end point of the axis. + * + * Results: + * The return value is a standard TCL result. + * + * Side Effects: + * Axis resources are allocated (GC, font). Axis layout is deferred until + * the height and width of the window are known. + * + *--------------------------------------------------------------------------- + */ +static void +ResetTextStyles(Axis *axisPtr) +{ + Graph *graphPtr = axisPtr->obj.graphPtr; + GC newGC; + XGCValues gcValues; + unsigned long gcMask; + + Blt_Ts_ResetStyle(graphPtr->tkwin, &axisPtr->limitsTextStyle); + + gcMask = (GCForeground | GCLineWidth | GCCapStyle); + gcValues.foreground = axisPtr->tickColor->pixel; + gcValues.font = Blt_FontId(axisPtr->tickFont); + gcValues.line_width = LineWidth(axisPtr->lineWidth); + gcValues.cap_style = CapProjecting; + + newGC = Tk_GetGC(graphPtr->tkwin, gcMask, &gcValues); + if (axisPtr->tickGC != NULL) { + Tk_FreeGC(graphPtr->display, axisPtr->tickGC); + } + axisPtr->tickGC = newGC; + + /* Assuming settings from above GC */ + gcValues.foreground = axisPtr->activeFgColor->pixel; + newGC = Tk_GetGC(graphPtr->tkwin, gcMask, &gcValues); + if (axisPtr->activeTickGC != NULL) { + Tk_FreeGC(graphPtr->display, axisPtr->activeTickGC); + } + axisPtr->activeTickGC = newGC; + + gcValues.background = gcValues.foreground = axisPtr->major.color->pixel; + gcValues.line_width = LineWidth(axisPtr->major.lineWidth); + gcMask = (GCForeground | GCBackground | GCLineWidth); + if (LineIsDashed(axisPtr->major.dashes)) { + gcValues.line_style = LineOnOffDash; + gcMask |= GCLineStyle; + } + newGC = Blt_GetPrivateGC(graphPtr->tkwin, gcMask, &gcValues); + if (LineIsDashed(axisPtr->major.dashes)) { + Blt_SetDashes(graphPtr->display, newGC, &axisPtr->major.dashes); + } + if (axisPtr->major.gc != NULL) { + Blt_FreePrivateGC(graphPtr->display, axisPtr->major.gc); + } + axisPtr->major.gc = newGC; + + gcValues.background = gcValues.foreground = axisPtr->minor.color->pixel; + gcValues.line_width = LineWidth(axisPtr->minor.lineWidth); + gcMask = (GCForeground | GCBackground | GCLineWidth); + if (LineIsDashed(axisPtr->minor.dashes)) { + gcValues.line_style = LineOnOffDash; + gcMask |= GCLineStyle; + } + newGC = Blt_GetPrivateGC(graphPtr->tkwin, gcMask, &gcValues); + if (LineIsDashed(axisPtr->minor.dashes)) { + Blt_SetDashes(graphPtr->display, newGC, &axisPtr->minor.dashes); + } + if (axisPtr->minor.gc != NULL) { + Blt_FreePrivateGC(graphPtr->display, axisPtr->minor.gc); + } + axisPtr->minor.gc = newGC; +} + +/* + *--------------------------------------------------------------------------- + * + * DestroyAxis -- + * + * Results: + * None. + * + * Side effects: + * Resources (font, color, gc, labels, etc.) associated with the axis are + * deallocated. + * + *--------------------------------------------------------------------------- + */ +static void +DestroyAxis(Axis *axisPtr) +{ + Graph *graphPtr = axisPtr->obj.graphPtr; + int flags; + + flags = Blt_GraphType(graphPtr); + Blt_FreeOptions(configSpecs, (char *)axisPtr, graphPtr->display, flags); + if (graphPtr->bindTable != NULL) { + Blt_DeleteBindings(graphPtr->bindTable, axisPtr); + } + if (axisPtr->link != NULL) { + Blt_Chain_DeleteLink(axisPtr->chain, axisPtr->link); + } + if (axisPtr->obj.name != NULL) { + Blt_Free(axisPtr->obj.name); + } + if (axisPtr->hashPtr != NULL) { + Blt_DeleteHashEntry(&graphPtr->axes.table, axisPtr->hashPtr); + } + Blt_Ts_FreeStyle(graphPtr->display, &axisPtr->limitsTextStyle); + + if (axisPtr->tickGC != NULL) { + Tk_FreeGC(graphPtr->display, axisPtr->tickGC); + } + if (axisPtr->activeTickGC != NULL) { + Tk_FreeGC(graphPtr->display, axisPtr->activeTickGC); + } + if (axisPtr->major.gc != NULL) { + Blt_FreePrivateGC(graphPtr->display, axisPtr->major.gc); + } + if (axisPtr->minor.gc != NULL) { + Blt_FreePrivateGC(graphPtr->display, axisPtr->minor.gc); + } + FreeTickLabels(axisPtr->tickLabels); + Blt_Chain_Destroy(axisPtr->tickLabels); + if (axisPtr->segments != NULL) { + Blt_Free(axisPtr->segments); + } + Blt_Free(axisPtr); +} + +static void +FreeAxis(DestroyData data) +{ + Axis *axisPtr = (Axis *)data; + DestroyAxis(axisPtr); +} + +static float titleAngle[4] = /* Rotation for each axis title */ +{ + 0.0, 90.0, 0.0, 270.0 +}; + +/* + *--------------------------------------------------------------------------- + * + * AxisOffsets -- + * + * Determines the sites of the axis, major and minor ticks, and title of + * the axis. + * + * Results: + * None. + * + *--------------------------------------------------------------------------- + */ +static void +AxisOffsets( + Axis *axisPtr, + int margin, + int offset, + AxisInfo *infoPtr) +{ + Graph *graphPtr = axisPtr->obj.graphPtr; + Margin *marginPtr; + int pad; /* Offset of axis from interior + * region. This includes a possible + * border and the axis line width. */ + int axisLine; + int t1, t2, labelOffset; + int tickLabel, axisPad; + int inset, mark; + int x, y; + float fangle; + + axisPtr->titleAngle = titleAngle[margin]; + marginPtr = graphPtr->margins + margin; + + tickLabel = axisLine = t1 = t2 = 0; + labelOffset = AXIS_PAD_TITLE; + if (axisPtr->lineWidth > 0) { + if (axisPtr->flags & AXIS_SHOWTICKS) { + t1 = axisPtr->tickLength; + t2 = (t1 * 10) / 15; + } + labelOffset = t1 + AXIS_PAD_TITLE; + if (axisPtr->flags & AXIS_EXTERIOR) { + labelOffset += axisPtr->lineWidth; + } + } + axisPad = 0; + if (graphPtr->plotRelief != TK_RELIEF_SOLID) { + axisPad = 0; + } + /* Adjust offset for the interior border width and the line width */ + pad = 1; + if (graphPtr->plotBW > 0) { + pad += graphPtr->plotBW + 1; + } + pad = 0; /* FIXME: test */ + /* + * Pre-calculate the x-coordinate positions of the axis, tick labels, and + * the individual major and minor ticks. + */ + inset = pad + axisPtr->lineWidth / 2; + switch (margin) { + case MARGIN_TOP: + axisLine = graphPtr->top; + if (axisPtr->flags & AXIS_EXTERIOR) { + axisLine -= graphPtr->plotBW + axisPad + axisPtr->lineWidth / 2; + tickLabel = axisLine - 2; + if (axisPtr->lineWidth > 0) { + tickLabel -= axisPtr->tickLength; + } +#ifdef notdef + fprintf(stderr, "axisLine=%d, axisPad=%d plotBW=%d axisPtr->lineWidth/2=%d\n", axisLine, axisPad, graphPtr->plotBW, axisPtr->lineWidth /2 ); +#endif + } else { + if (graphPtr->plotRelief == TK_RELIEF_SOLID) { + axisLine--; + } + axisLine -= axisPad + axisPtr->lineWidth / 2; + tickLabel = graphPtr->top - graphPtr->plotBW - 2; +#ifdef notdef + fprintf(stderr, "axisLine=%d, axisPad=%d plotBW=%d axisPtr->lineWidth/2=%d\n", axisLine, axisPad, graphPtr->plotBW, axisPtr->lineWidth /2 ); +#endif + } + mark = graphPtr->top - offset - pad; + axisPtr->tickAnchor = TK_ANCHOR_S; + axisPtr->left = axisPtr->screenMin - inset - 2; + axisPtr->right = axisPtr->screenMin + axisPtr->screenRange + inset - 1; + if (graphPtr->stackAxes) { + axisPtr->top = mark - marginPtr->axesOffset; + } else { + axisPtr->top = mark - axisPtr->height; + } + axisPtr->bottom = mark; + if (axisPtr->titleAlternate) { + x = graphPtr->right + AXIS_PAD_TITLE; + y = mark - (axisPtr->height / 2); + axisPtr->titleAnchor = TK_ANCHOR_W; + } else { + x = (axisPtr->right + axisPtr->left) / 2; + if (graphPtr->stackAxes) { + y = mark - marginPtr->axesOffset + AXIS_PAD_TITLE; + } else { + y = mark - axisPtr->height + AXIS_PAD_TITLE; + } + axisPtr->titleAnchor = TK_ANCHOR_N; + } + axisPtr->titlePos.x = x; + axisPtr->titlePos.y = y; + break; + + case MARGIN_BOTTOM: + /* + * ----------- bottom + plot borderwidth + * mark -------------------------------------------- + * ===================== axisLine (linewidth) + * tick + * title + * + * ===================== axisLine (linewidth) + * ----------- bottom + plot borderwidth + * mark -------------------------------------------- + * tick + * title + */ + axisLine = graphPtr->bottom; + if (graphPtr->plotRelief == TK_RELIEF_SOLID) { + axisLine++; + } + if (axisPtr->flags & AXIS_EXTERIOR) { + axisLine += graphPtr->plotBW + axisPad + axisPtr->lineWidth / 2; + tickLabel = axisLine + 2; + if (axisPtr->lineWidth > 0) { + tickLabel += axisPtr->tickLength; + } + } else { + axisLine -= axisPad + axisPtr->lineWidth / 2; + tickLabel = graphPtr->bottom + graphPtr->plotBW + 2; + } + mark = graphPtr->bottom + offset; + fangle = FMOD(axisPtr->tickAngle, 90.0); + if (fangle == 0.0) { + axisPtr->tickAnchor = TK_ANCHOR_N; + } else { + int quadrant; + + quadrant = (int)(axisPtr->tickAngle / 90.0); + if ((quadrant == 0) || (quadrant == 2)) { + axisPtr->tickAnchor = TK_ANCHOR_NE; + } else { + axisPtr->tickAnchor = TK_ANCHOR_NW; + } + } + axisPtr->left = axisPtr->screenMin - inset - 2; + axisPtr->right = axisPtr->screenMin + axisPtr->screenRange + inset - 1; + axisPtr->top = graphPtr->bottom + labelOffset - t1; + if (graphPtr->stackAxes) { + axisPtr->bottom = mark + marginPtr->axesOffset - 1; + } else { + axisPtr->bottom = mark + axisPtr->height - 1; + } + if (axisPtr->titleAlternate) { + x = graphPtr->right + AXIS_PAD_TITLE; + y = mark + (axisPtr->height / 2); + axisPtr->titleAnchor = TK_ANCHOR_W; + } else { + x = (axisPtr->right + axisPtr->left) / 2; + if (graphPtr->stackAxes) { + y = mark + marginPtr->axesOffset - AXIS_PAD_TITLE; + } else { + y = mark + axisPtr->height - AXIS_PAD_TITLE; + } + axisPtr->titleAnchor = TK_ANCHOR_S; + } + axisPtr->titlePos.x = x; + axisPtr->titlePos.y = y; + break; + + case MARGIN_LEFT: + /* + * mark + * | : + * | : + * | : + * | : + * | : + * axisLine + */ + /* + * Exterior axis + * + plotarea right + * |A|B|C|D|E|F|G|H + * |right + * A = plot pad + * B = plot border width + * C = axis pad + * D = axis line + * E = tick length + * F = tick label + * G = graph border width + * H = highlight thickness + */ + /* + * Interior axis + * + plotarea right + * |A|B|C|D|E|F|G|H + * |right + * A = plot pad + * B = tick length + * C = axis line width + * D = axis pad + * E = plot border width + * F = tick label + * G = graph border width + * H = highlight thickness + */ + axisLine = graphPtr->left; + if (axisPtr->flags & AXIS_EXTERIOR) { + axisLine -= graphPtr->plotBW + axisPad + axisPtr->lineWidth / 2; + tickLabel = axisLine - 2; + if (axisPtr->lineWidth > 0) { + tickLabel -= axisPtr->tickLength; + } + } else { + if (graphPtr->plotRelief == TK_RELIEF_SOLID) { + axisLine--; + } + axisLine += axisPad + axisPtr->lineWidth / 2; + tickLabel = graphPtr->left - graphPtr->plotBW - 2; + } + mark = graphPtr->left - offset; + axisPtr->tickAnchor = TK_ANCHOR_E; + if (graphPtr->stackAxes) { + axisPtr->left = mark - marginPtr->axesOffset; + } else { + axisPtr->left = mark - axisPtr->width; + } + axisPtr->right = mark - 3; + axisPtr->top = axisPtr->screenMin - inset - 2; + axisPtr->bottom = axisPtr->screenMin + axisPtr->screenRange + inset - 1; + if (axisPtr->titleAlternate) { + x = mark - (axisPtr->width / 2); + y = graphPtr->top - AXIS_PAD_TITLE; + axisPtr->titleAnchor = TK_ANCHOR_SW; + } else { + if (graphPtr->stackAxes) { + x = mark - marginPtr->axesOffset; + } else { + x = mark - axisPtr->width + AXIS_PAD_TITLE; + } + y = (axisPtr->bottom + axisPtr->top) / 2; + axisPtr->titleAnchor = TK_ANCHOR_W; + } + axisPtr->titlePos.x = x; + axisPtr->titlePos.y = y; + break; + + case MARGIN_RIGHT: + axisLine = graphPtr->right; + if (graphPtr->plotRelief == TK_RELIEF_SOLID) { + axisLine++; /* Draw axis line within solid plot + * border. */ + } + if (axisPtr->flags & AXIS_EXTERIOR) { + axisLine += graphPtr->plotBW + axisPad + axisPtr->lineWidth / 2; + tickLabel = axisLine + 2; + if (axisPtr->lineWidth > 0) { + tickLabel += axisPtr->tickLength; + } + } else { + axisLine -= axisPad + axisPtr->lineWidth / 2; + tickLabel = graphPtr->right + graphPtr->plotBW + 2; + } + mark = graphPtr->right + offset + pad; + axisPtr->tickAnchor = TK_ANCHOR_W; + axisPtr->left = mark; + if (graphPtr->stackAxes) { + axisPtr->right = mark + marginPtr->axesOffset - 1; + } else { + axisPtr->right = mark + axisPtr->width - 1; + } + axisPtr->top = axisPtr->screenMin - inset - 2; + axisPtr->bottom = axisPtr->screenMin + axisPtr->screenRange + inset -1; + if (axisPtr->titleAlternate) { + x = mark + (axisPtr->width / 2); + y = graphPtr->top - AXIS_PAD_TITLE; + axisPtr->titleAnchor = TK_ANCHOR_SE; + } else { + if (graphPtr->stackAxes) { + x = mark + marginPtr->axesOffset - AXIS_PAD_TITLE; + } else { + x = mark + axisPtr->width - AXIS_PAD_TITLE; + } + y = (axisPtr->bottom + axisPtr->top) / 2; + axisPtr->titleAnchor = TK_ANCHOR_E; + } + axisPtr->titlePos.x = x; + axisPtr->titlePos.y = y; + break; + + case MARGIN_NONE: + axisLine = 0; + break; + } + if ((margin == MARGIN_LEFT) || (margin == MARGIN_TOP)) { + t1 = -t1, t2 = -t2; + labelOffset = -labelOffset; + } + infoPtr->axis = axisLine; + infoPtr->t1 = axisLine + t1; + infoPtr->t2 = axisLine + t2; + if (tickLabel > 0) { + infoPtr->label = tickLabel; + } else { + infoPtr->label = axisLine + labelOffset; + } + if ((axisPtr->flags & AXIS_EXTERIOR) == 0) { + /*infoPtr->label = axisLine + labelOffset - t1; */ + infoPtr->t1 = axisLine - t1; + infoPtr->t2 = axisLine - t2; + } +} + +static void +MakeAxisLine(Axis *axisPtr, int line, Segment2d *sp) +{ + double min, max; + + min = axisPtr->axisRange.min; + max = axisPtr->axisRange.max; + if (axisPtr->logScale) { + min = EXP10(min); + max = EXP10(max); + } + if (AxisIsHorizontal(axisPtr)) { + sp->p.x = Blt_HMap(axisPtr, min); + sp->q.x = Blt_HMap(axisPtr, max); + sp->p.y = sp->q.y = line; + } else { + sp->q.x = sp->p.x = line; + sp->p.y = Blt_VMap(axisPtr, min); + sp->q.y = Blt_VMap(axisPtr, max); + } +} + + +static void +MakeTick(Axis *axisPtr, double value, int tick, int line, Segment2d *sp) +{ + if (axisPtr->logScale) { + value = EXP10(value); + } + if (AxisIsHorizontal(axisPtr)) { + sp->p.x = sp->q.x = Blt_HMap(axisPtr, value); + sp->p.y = line; + sp->q.y = tick; + } else { + sp->p.x = line; + sp->p.y = sp->q.y = Blt_VMap(axisPtr, value); + sp->q.x = tick; + } +} + +static void +MakeSegments(Axis *axisPtr, AxisInfo *infoPtr) +{ + int arraySize; + int nMajorTicks, nMinorTicks; + Segment2d *segments; + Segment2d *sp; + + if (axisPtr->segments != NULL) { + Blt_Free(axisPtr->segments); + } + nMajorTicks = nMinorTicks = 0; + if (axisPtr->t1Ptr != NULL) { + nMajorTicks = axisPtr->t1Ptr->nTicks; + } + if (axisPtr->t2Ptr != NULL) { + nMinorTicks = axisPtr->t2Ptr->nTicks; + } + arraySize = 1 + (nMajorTicks * (nMinorTicks + 1)); + segments = Blt_AssertMalloc(arraySize * sizeof(Segment2d)); + sp = segments; + if (axisPtr->lineWidth > 0) { + /* Axis baseline */ + MakeAxisLine(axisPtr, infoPtr->axis, sp); + sp++; + } + if (axisPtr->flags & AXIS_SHOWTICKS) { + Blt_ChainLink link; + double labelPos; + int i; + int isHoriz; + + isHoriz = AxisIsHorizontal(axisPtr); + for (i = 0; i < nMajorTicks; i++) { + double t1, t2; + int j; + + t1 = axisPtr->t1Ptr->values[i]; + /* Minor ticks */ + for (j = 0; j < nMinorTicks; j++) { + t2 = t1 + (axisPtr->majorSweep.step * + axisPtr->t2Ptr->values[j]); + if (InRange(t2, &axisPtr->axisRange)) { + MakeTick(axisPtr, t2, infoPtr->t2, infoPtr->axis, sp); + sp++; + } + } + if (!InRange(t1, &axisPtr->axisRange)) { + continue; + } + /* Major tick */ + MakeTick(axisPtr, t1, infoPtr->t1, infoPtr->axis, sp); + sp++; + } + + link = Blt_Chain_FirstLink(axisPtr->tickLabels); + labelPos = (double)infoPtr->label; + + for (i = 0; i < nMajorTicks; i++) { + double t1; + TickLabel *labelPtr; + Segment2d seg; + + t1 = axisPtr->t1Ptr->values[i]; + if (axisPtr->labelOffset) { + t1 += axisPtr->majorSweep.step * 0.5; + } + if (!InRange(t1, &axisPtr->axisRange)) { + continue; + } + labelPtr = Blt_Chain_GetValue(link); + link = Blt_Chain_NextLink(link); + MakeTick(axisPtr, t1, infoPtr->t1, infoPtr->axis, &seg); + /* Save tick label X-Y position. */ + if (isHoriz) { + labelPtr->anchorPos.x = seg.p.x; + labelPtr->anchorPos.y = labelPos; + } else { + labelPtr->anchorPos.x = labelPos; + labelPtr->anchorPos.y = seg.p.y; + } + } + } + axisPtr->segments = segments; + axisPtr->nSegments = sp - segments; + assert(axisPtr->nSegments <= arraySize); +} + +/* + *--------------------------------------------------------------------------- + * + * MapAxis -- + * + * Pre-calculates positions of the axis, ticks, and labels (to be used + * later when displaying the axis). Calculates the values for each major + * and minor tick and checks to see if they are in range (the outer ticks + * may be outside of the range of plotted values). + * + * Line segments for the minor and major ticks are saved into one + * XSegment array so that they can be drawn by a single XDrawSegments + * call. The positions of the tick labels are also computed and saved. + * + * Results: + * None. + * + * Side Effects: + * Line segments and tick labels are saved and used later to draw the + * axis. + * + *--------------------------------------------------------------------------- + */ +static void +MapAxis(Axis *axisPtr, int offset, int margin) +{ + AxisInfo info; + Graph *graphPtr = axisPtr->obj.graphPtr; + + if (AxisIsHorizontal(axisPtr)) { + axisPtr->screenMin = graphPtr->hOffset; + axisPtr->width = graphPtr->right - graphPtr->left; + axisPtr->screenRange = graphPtr->hRange; + } else { + axisPtr->screenMin = graphPtr->vOffset; + axisPtr->height = graphPtr->bottom - graphPtr->top; + axisPtr->screenRange = graphPtr->vRange; + } + axisPtr->screenScale = 1.0 / axisPtr->screenRange; + AxisOffsets(axisPtr, margin, offset, &info); + MakeSegments(axisPtr, &info); +} + +/* + *--------------------------------------------------------------------------- + * + * MapStackedAxis -- + * + * Pre-calculates positions of the axis, ticks, and labels (to be used + * later when displaying the axis). Calculates the values for each major + * and minor tick and checks to see if they are in range (the outer ticks + * may be outside of the range of plotted values). + * + * Line segments for the minor and major ticks are saved into one XSegment + * array so that they can be drawn by a single XDrawSegments call. The + * positions of the tick labels are also computed and saved. + * + * Results: + * None. + * + * Side Effects: + * Line segments and tick labels are saved and used later to draw the + * axis. + * + *--------------------------------------------------------------------------- + */ +static void +MapStackedAxis(Axis *axisPtr, int count, int margin) +{ + AxisInfo info; + Graph *graphPtr = axisPtr->obj.graphPtr; + unsigned int slice, w, h; + + if ((graphPtr->margins[axisPtr->margin].axes->nLinks > 1) || + (axisPtr->reqNumMajorTicks <= 0)) { + axisPtr->reqNumMajorTicks = 4; + } + if (AxisIsHorizontal(axisPtr)) { + slice = graphPtr->hRange / graphPtr->margins[margin].axes->nLinks; + axisPtr->screenMin = graphPtr->hOffset; + axisPtr->width = slice; + } else { + slice = graphPtr->vRange / graphPtr->margins[margin].axes->nLinks; + axisPtr->screenMin = graphPtr->vOffset; + axisPtr->height = slice; + } +#define AXIS_PAD 2 + Blt_GetTextExtents(axisPtr->tickFont, 0, "0", 1, &w, &h); + axisPtr->screenMin += (slice * count) + AXIS_PAD + h / 2; + axisPtr->screenRange = slice - 2 * AXIS_PAD - h; + axisPtr->screenScale = 1.0f / axisPtr->screenRange; + AxisOffsets(axisPtr, margin, 0, &info); + MakeSegments(axisPtr, &info); +} + +/* + *--------------------------------------------------------------------------- + * + * AdjustViewport -- + * + * Adjusts the offsets of the viewport according to the scroll mode. This + * is to accommodate both "listbox" and "canvas" style scrolling. + * + * "canvas" The viewport scrolls within the range of world + * coordinates. This way the viewport always displays + * a full page of the world. If the world is smaller + * than the viewport, then (bizarrely) the world and + * viewport are inverted so that the world moves up + * and down within the viewport. + * + * "listbox" The viewport can scroll beyond the range of world + * coordinates. Every entry can be displayed at the + * top of the viewport. This also means that the + * scrollbar thumb weirdly shrinks as the last entry + * is scrolled upward. + * + * Results: + * The corrected offset is returned. + * + *--------------------------------------------------------------------------- + */ +static double +AdjustViewport(double offset, double windowSize) +{ + /* + * Canvas-style scrolling allows the world to be scrolled within the window. + */ + if (windowSize > 1.0) { + if (windowSize < (1.0 - offset)) { + offset = 1.0 - windowSize; + } + if (offset > 0.0) { + offset = 0.0; + } + } else { + if ((offset + windowSize) > 1.0) { + offset = 1.0 - windowSize; + } + if (offset < 0.0) { + offset = 0.0; + } + } + return offset; +} + +static int +GetAxisScrollInfo( + Tcl_Interp *interp, + int objc, + Tcl_Obj *const *objv, + double *offsetPtr, + double windowSize, + double scrollUnits, + double scale) +{ + const char *string; + char c; + double offset; + int length; + + offset = *offsetPtr; + string = Tcl_GetStringFromObj(objv[0], &length); + c = string[0]; + scrollUnits *= scale; + if ((c == 's') && (strncmp(string, "scroll", length) == 0)) { + int count; + double fract; + + assert(objc == 3); + /* Scroll number unit/page */ + if (Tcl_GetIntFromObj(interp, objv[1], &count) != TCL_OK) { + return TCL_ERROR; + } + string = Tcl_GetStringFromObj(objv[2], &length); + c = string[0]; + if ((c == 'u') && (strncmp(string, "units", length) == 0)) { + fract = count * scrollUnits; + } else if ((c == 'p') && (strncmp(string, "pages", length) == 0)) { + /* A page is 90% of the view-able window. */ + fract = (int)(count * windowSize * 0.9 + 0.5); + } else if ((c == 'p') && (strncmp(string, "pixels", length) == 0)) { + fract = count * scale; + } else { + Tcl_AppendResult(interp, "unknown \"scroll\" units \"", string, + "\"", (char *)NULL); + return TCL_ERROR; + } + offset += fract; + } else if ((c == 'm') && (strncmp(string, "moveto", length) == 0)) { + double fract; + + assert(objc == 2); + /* moveto fraction */ + if (Blt_GetDoubleFromObj(interp, objv[1], &fract) != TCL_OK) { + return TCL_ERROR; + } + offset = fract; + } else { + int count; + double fract; + + /* Treat like "scroll units" */ + if (Tcl_GetIntFromObj(interp, objv[0], &count) != TCL_OK) { + return TCL_ERROR; + } + fract = (double)count * scrollUnits; + offset += fract; + /* CHECK THIS: return TCL_OK; */ + } + *offsetPtr = AdjustViewport(offset, windowSize); + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * DrawAxis -- + * + * Draws the axis, ticks, and labels onto the canvas. + * + * Initializes and passes text attribute information through TextStyle + * structure. + * + * Results: + * None. + * + * Side Effects: + * Axis gets drawn on window. + * + *--------------------------------------------------------------------------- + */ +static void +DrawAxis(Axis *axisPtr, Drawable drawable) +{ + Graph *graphPtr = axisPtr->obj.graphPtr; + + if (axisPtr->normalBg != NULL) { + Blt_FillBackgroundRectangle(graphPtr->tkwin, drawable, + axisPtr->normalBg, + axisPtr->left, axisPtr->top, + axisPtr->right - axisPtr->left, + axisPtr->bottom - axisPtr->top, axisPtr->borderWidth, + axisPtr->relief); + } + if (axisPtr->title != NULL) { + TextStyle ts; + + Blt_Ts_InitStyle(ts); + Blt_Ts_SetAngle(ts, axisPtr->titleAngle); + Blt_Ts_SetFont(ts, axisPtr->titleFont); + Blt_Ts_SetPadding(ts, 1, 2, 0, 0); + Blt_Ts_SetAnchor(ts, axisPtr->titleAnchor); + Blt_Ts_SetJustify(ts, axisPtr->titleJustify); + if (axisPtr->flags & ACTIVE) { + Blt_Ts_SetForeground(ts, axisPtr->activeFgColor); + } else { + Blt_Ts_SetForeground(ts, axisPtr->titleColor); + } + Blt_Ts_SetForeground(ts, axisPtr->titleColor); + if ((axisPtr->titleAngle == 90.0) || (axisPtr->titleAngle == 270.0)) { + Blt_Ts_SetMaxLength(ts, axisPtr->height); + } else { + Blt_Ts_SetMaxLength(ts, axisPtr->width); + } + Blt_Ts_DrawText(graphPtr->tkwin, drawable, axisPtr->title, -1, &ts, + (int)axisPtr->titlePos.x, (int)axisPtr->titlePos.y); + } + if (axisPtr->scrollCmdObjPtr != NULL) { + double viewWidth, viewMin, viewMax; + double worldWidth, worldMin, worldMax; + double fract; + int isHoriz; + + worldMin = axisPtr->valueRange.min; + worldMax = axisPtr->valueRange.max; + if (DEFINED(axisPtr->scrollMin)) { + worldMin = axisPtr->scrollMin; + } + if (DEFINED(axisPtr->scrollMax)) { + worldMax = axisPtr->scrollMax; + } + viewMin = axisPtr->min; + viewMax = axisPtr->max; + if (viewMin < worldMin) { + viewMin = worldMin; + } + if (viewMax > worldMax) { + viewMax = worldMax; + } + if (axisPtr->logScale) { + worldMin = log10(worldMin); + worldMax = log10(worldMax); + viewMin = log10(viewMin); + viewMax = log10(viewMax); + } + worldWidth = worldMax - worldMin; + viewWidth = viewMax - viewMin; + isHoriz = AxisIsHorizontal(axisPtr); + + if (isHoriz != axisPtr->descending) { + fract = (viewMin - worldMin) / worldWidth; + } else { + fract = (worldMax - viewMax) / worldWidth; + } + fract = AdjustViewport(fract, viewWidth / worldWidth); + + if (isHoriz != axisPtr->descending) { + viewMin = (fract * worldWidth); + axisPtr->min = viewMin + worldMin; + axisPtr->max = axisPtr->min + viewWidth; + viewMax = viewMin + viewWidth; + if (axisPtr->logScale) { + axisPtr->min = EXP10(axisPtr->min); + axisPtr->max = EXP10(axisPtr->max); + } + Blt_UpdateScrollbar(graphPtr->interp, axisPtr->scrollCmdObjPtr, + viewMin, viewMax, worldWidth); + } else { + viewMax = (fract * worldWidth); + axisPtr->max = worldMax - viewMax; + axisPtr->min = axisPtr->max - viewWidth; + viewMin = viewMax + viewWidth; + if (axisPtr->logScale) { + axisPtr->min = EXP10(axisPtr->min); + axisPtr->max = EXP10(axisPtr->max); + } + Blt_UpdateScrollbar(graphPtr->interp, axisPtr->scrollCmdObjPtr, + viewMax, viewMin, worldWidth); + } + } + if (axisPtr->flags & AXIS_SHOWTICKS) { + Blt_ChainLink link; + TextStyle ts; + + Blt_Ts_InitStyle(ts); + Blt_Ts_SetAngle(ts, axisPtr->tickAngle); + Blt_Ts_SetFont(ts, axisPtr->tickFont); + Blt_Ts_SetPadding(ts, 2, 0, 0, 0); + Blt_Ts_SetAnchor(ts, axisPtr->tickAnchor); + if (axisPtr->flags & ACTIVE) { + Blt_Ts_SetForeground(ts, axisPtr->activeFgColor); + } else { + Blt_Ts_SetForeground(ts, axisPtr->tickColor); + } + for (link = Blt_Chain_FirstLink(axisPtr->tickLabels); link != NULL; + link = Blt_Chain_NextLink(link)) { + TickLabel *labelPtr; + + labelPtr = Blt_Chain_GetValue(link); + /* Draw major tick labels */ + Blt_DrawText(graphPtr->tkwin, drawable, labelPtr->string, &ts, + (int)labelPtr->anchorPos.x, (int)labelPtr->anchorPos.y); + } + } + if ((axisPtr->nSegments > 0) && (axisPtr->lineWidth > 0)) { + GC gc; + + if (axisPtr->flags & ACTIVE) { + gc = axisPtr->activeTickGC; + } else { + gc = axisPtr->tickGC; + } + /* Draw the tick marks and axis line. */ + Blt_Draw2DSegments(graphPtr->display, drawable, gc, axisPtr->segments, + axisPtr->nSegments); + } +} + +/* + *--------------------------------------------------------------------------- + * + * AxisToPostScript -- + * + * Generates PostScript output to draw the axis, ticks, and labels. + * + * Initializes and passes text attribute information through TextStyle + * structure. + * + * Results: + * None. + * + * Side Effects: + * PostScript output is left in graphPtr->interp->result; + * + *--------------------------------------------------------------------------- + */ +/* ARGSUSED */ +static void +AxisToPostScript(Blt_Ps ps, Axis *axisPtr) +{ + Blt_Ps_Format(ps, "%% Axis \"%s\"\n", axisPtr->obj.name); + if (axisPtr->normalBg != NULL) { + Tk_3DBorder border; + + border = Blt_BackgroundBorder(axisPtr->normalBg); + Blt_Ps_Fill3DRectangle(ps, border, + (double)axisPtr->left, (double)axisPtr->top, + axisPtr->right - axisPtr->left, axisPtr->bottom - axisPtr->top, + axisPtr->borderWidth, axisPtr->relief); + } + if (axisPtr->title != NULL) { + TextStyle ts; + + Blt_Ts_InitStyle(ts); + Blt_Ts_SetAngle(ts, axisPtr->titleAngle); + Blt_Ts_SetFont(ts, axisPtr->titleFont); + Blt_Ts_SetPadding(ts, 1, 2, 0, 0); + Blt_Ts_SetAnchor(ts, axisPtr->titleAnchor); + Blt_Ts_SetJustify(ts, axisPtr->titleJustify); + Blt_Ts_SetForeground(ts, axisPtr->titleColor); + Blt_Ps_DrawText(ps, axisPtr->title, &ts, axisPtr->titlePos.x, + axisPtr->titlePos.y); + } + if (axisPtr->flags & AXIS_SHOWTICKS) { + Blt_ChainLink link; + TextStyle ts; + + Blt_Ts_InitStyle(ts); + Blt_Ts_SetAngle(ts, axisPtr->tickAngle); + Blt_Ts_SetFont(ts, axisPtr->tickFont); + Blt_Ts_SetPadding(ts, 2, 0, 0, 0); + Blt_Ts_SetAnchor(ts, axisPtr->tickAnchor); + Blt_Ts_SetForeground(ts, axisPtr->tickColor); + + for (link = Blt_Chain_FirstLink(axisPtr->tickLabels); link != NULL; + link = Blt_Chain_NextLink(link)) { + TickLabel *labelPtr; + + labelPtr = Blt_Chain_GetValue(link); + Blt_Ps_DrawText(ps, labelPtr->string, &ts, labelPtr->anchorPos.x, + labelPtr->anchorPos.y); + } + } + if ((axisPtr->nSegments > 0) && (axisPtr->lineWidth > 0)) { + Blt_Ps_XSetLineAttributes(ps, axisPtr->tickColor, axisPtr->lineWidth, + (Blt_Dashes *)NULL, CapButt, JoinMiter); + Blt_Ps_Draw2DSegments(ps, axisPtr->segments, axisPtr->nSegments); + } +} + +static void +MakeGridLine(Axis *axisPtr, double value, Segment2d *sp) +{ + Graph *graphPtr = axisPtr->obj.graphPtr; + + if (axisPtr->logScale) { + value = EXP10(value); + } + /* Grid lines run orthogonally to the axis */ + if (AxisIsHorizontal(axisPtr)) { + sp->p.y = graphPtr->top; + sp->q.y = graphPtr->bottom; + sp->p.x = sp->q.x = Blt_HMap(axisPtr, value); + } else { + sp->p.x = graphPtr->left; + sp->q.x = graphPtr->right; + sp->p.y = sp->q.y = Blt_VMap(axisPtr, value); + } +} + +/* + *--------------------------------------------------------------------------- + * + * MapGridlines -- + * + * Assembles the grid lines associated with an axis. Generates tick + * positions if necessary (this happens when the axis is not a logical axis + * too). + * + * Results: + * None. + * + *--------------------------------------------------------------------------- + */ +static void +MapGridlines(Axis *axisPtr) +{ + Segment2d *s1, *s2; + Ticks *t1Ptr, *t2Ptr; + int needed; + int i; + + if (axisPtr == NULL) { + return; + } + t1Ptr = axisPtr->t1Ptr; + if (t1Ptr == NULL) { + t1Ptr = GenerateTicks(&axisPtr->majorSweep); + } + t2Ptr = axisPtr->t2Ptr; + if (t2Ptr == NULL) { + t2Ptr = GenerateTicks(&axisPtr->minorSweep); + } + needed = t1Ptr->nTicks; + if (axisPtr->flags & AXIS_GRIDMINOR) { + needed += (t1Ptr->nTicks * t2Ptr->nTicks); + } + if (needed == 0) { + return; + } + needed = t1Ptr->nTicks; + if (needed != axisPtr->major.nAllocated) { + if (axisPtr->major.segments != NULL) { + Blt_Free(axisPtr->major.segments); + } + axisPtr->major.segments = Blt_AssertMalloc(sizeof(Segment2d) * needed); + axisPtr->major.nAllocated = needed; + } + needed = (t1Ptr->nTicks * t2Ptr->nTicks); + if (needed != axisPtr->minor.nAllocated) { + if (axisPtr->minor.segments != NULL) { + Blt_Free(axisPtr->minor.segments); + } + axisPtr->minor.segments = Blt_AssertMalloc(sizeof(Segment2d) * needed); + axisPtr->minor.nAllocated = needed; + } + s1 = axisPtr->major.segments, s2 = axisPtr->minor.segments; + for (i = 0; i < t1Ptr->nTicks; i++) { + double value; + + value = t1Ptr->values[i]; + if (axisPtr->flags & AXIS_GRIDMINOR) { + int j; + + for (j = 0; j < t2Ptr->nTicks; j++) { + double subValue; + + subValue = value + (axisPtr->majorSweep.step * + t2Ptr->values[j]); + if (InRange(subValue, &axisPtr->axisRange)) { + MakeGridLine(axisPtr, subValue, s2); + s2++; + } + } + } + if (InRange(value, &axisPtr->axisRange)) { + MakeGridLine(axisPtr, value, s1); + s1++; + } + } + if (t1Ptr != axisPtr->t1Ptr) { + Blt_Free(t1Ptr); /* Free generated ticks. */ + } + if (t2Ptr != axisPtr->t2Ptr) { + Blt_Free(t2Ptr); /* Free generated ticks. */ + } + axisPtr->major.nUsed = s1 - axisPtr->major.segments; + axisPtr->minor.nUsed = s2 - axisPtr->minor.segments; +} + +/* + *--------------------------------------------------------------------------- + * + * GetAxisGeometry -- + * + * Results: + * None. + * + * Exterior axis: + * l r + * |a|b|c|d|e|f|g|h|i| j |i|h|g|f|e|d|c|d|a| + * + * Interior axis: + * l r + * |a|b|c|d|h|g|f|e| j |e|f|g|h|d|c|b|a| + * i.. ..i + * a = highlight thickness + * b = graph borderwidth + * c = axis title + * d = tick label + * e = tick + * f = axis line + * g = 1 pixel pad + * h = plot borderwidth + * i = plot pad + * j = plot area + *--------------------------------------------------------------------------- + */ +static void +GetAxisGeometry(Graph *graphPtr, Axis *axisPtr) +{ + unsigned int y; + + FreeTickLabels(axisPtr->tickLabels); + y = 0; + + if ((axisPtr->flags & AXIS_EXTERIOR) && + (graphPtr->plotRelief != TK_RELIEF_SOLID)) { + /* Leave room for axis baseline and padding */ + y += axisPtr->lineWidth + 2; + } + + axisPtr->maxTickHeight = axisPtr->maxTickWidth = 0; + if (axisPtr->flags & AXIS_SHOWTICKS) { + unsigned int pad; + unsigned int i, nLabels, nTicks; + + SweepTicks(axisPtr); + + nTicks = 0; + if (axisPtr->t1Ptr != NULL) { + nTicks = axisPtr->t1Ptr->nTicks; + } + assert(nTicks <= MAXTICKS); + + nLabels = 0; + for (i = 0; i < nTicks; i++) { + TickLabel *labelPtr; + double x, x2; + unsigned int lw, lh; /* Label width and height. */ + + x2 = x = axisPtr->t1Ptr->values[i]; + if (axisPtr->labelOffset) { + x2 += axisPtr->majorSweep.step * 0.5; + } + if (!InRange(x2, &axisPtr->axisRange)) { + continue; + } + labelPtr = MakeLabel(axisPtr, x); + Blt_Chain_Append(axisPtr->tickLabels, labelPtr); + nLabels++; + /* + * Get the dimensions of each tick label. Remember tick labels + * can be multi-lined and/or rotated. + */ + Blt_GetTextExtents(axisPtr->tickFont, 0, labelPtr->string, -1, + &lw, &lh); + labelPtr->width = lw; + labelPtr->height = lh; + + if (axisPtr->tickAngle != 0.0f) { + double rlw, rlh; /* Rotated label width and height. */ + Blt_GetBoundingBox(lw, lh, axisPtr->tickAngle, &rlw, &rlh,NULL); + lw = ROUND(rlw), lh = ROUND(rlh); + } + if (axisPtr->maxTickWidth < lw) { + axisPtr->maxTickWidth = lw; + } + if (axisPtr->maxTickHeight < lh) { + axisPtr->maxTickHeight = lh; + } + } + assert(nLabels <= nTicks); + + pad = 0; + if (axisPtr->flags & AXIS_EXTERIOR) { + /* Because the axis cap style is "CapProjecting", we need to + * account for an extra 1.5 linewidth at the end of each line. */ + pad = ((axisPtr->lineWidth * 12) / 8); + } + if (AxisIsHorizontal(axisPtr)) { + y += axisPtr->maxTickHeight + pad; + } else { + y += axisPtr->maxTickWidth + pad; + if (axisPtr->maxTickWidth > 0) { + y += 5; /* Pad either size of label. */ + } + } + y += 2 * AXIS_PAD_TITLE; + if ((axisPtr->lineWidth > 0) && (axisPtr->flags & AXIS_EXTERIOR)) { + /* Distance from axis line to tick label. */ + y += axisPtr->tickLength; + } + } + + if (axisPtr->title != NULL) { + if (axisPtr->titleAlternate) { + if (y < axisPtr->titleHeight) { + y = axisPtr->titleHeight; + } + } else { + y += axisPtr->titleHeight + AXIS_PAD_TITLE; + } + } + + /* Correct for orientation of the axis. */ + if (AxisIsHorizontal(axisPtr)) { + axisPtr->height = y; + } else { + axisPtr->width = y; + } +} + +/* + *--------------------------------------------------------------------------- + * + * GetMarginGeometry -- + * + * Examines all the axes in the given margin and determines the area + * required to display them. + * + * Note: For multiple axes, the titles are displayed in another + * margin. So we must keep track of the widest title. + * + * Results: + * Returns the width or height of the margin, depending if it runs + * horizontally along the graph or vertically. + * + * Side Effects: + * The area width and height set in the margin. Note again that this may + * be corrected later (mulitple axes) to adjust for the longest title in + * another margin. + * + *--------------------------------------------------------------------------- + */ +static int +GetMarginGeometry(Graph *graphPtr, Margin *marginPtr) +{ + Blt_ChainLink link; + unsigned int l, w, h; /* Length, width, and height. */ + int isHoriz; + unsigned int nVisible; + + isHoriz = HORIZMARGIN(marginPtr); + + /* Count the visible axes. */ + nVisible = 0; + l = w = h = 0; + marginPtr->maxTickWidth = marginPtr->maxTickHeight = 0; + if (graphPtr->stackAxes) { + for (link = Blt_Chain_FirstLink(marginPtr->axes); link != NULL; + link = Blt_Chain_NextLink(link)) { + Axis *axisPtr; + + axisPtr = Blt_Chain_GetValue(link); + if ((axisPtr->flags & (HIDE|AXIS_USE)) == AXIS_USE) { + nVisible++; + if (graphPtr->flags & GET_AXIS_GEOMETRY) { + GetAxisGeometry(graphPtr, axisPtr); + } + if (isHoriz) { + if (h < axisPtr->height) { + h = axisPtr->height; + } + } else { + if (w < axisPtr->width) { + w = axisPtr->width; + } + } + if (axisPtr->maxTickWidth > marginPtr->maxTickWidth) { + marginPtr->maxTickWidth = axisPtr->maxTickWidth; + } + if (axisPtr->maxTickHeight > marginPtr->maxTickHeight) { + marginPtr->maxTickHeight = axisPtr->maxTickHeight; + } + } + } + } else { + for (link = Blt_Chain_FirstLink(marginPtr->axes); link != NULL; + link = Blt_Chain_NextLink(link)) { + Axis *axisPtr; + + axisPtr = Blt_Chain_GetValue(link); + if ((axisPtr->flags & (HIDE|AXIS_USE)) == AXIS_USE) { + nVisible++; + if (graphPtr->flags & GET_AXIS_GEOMETRY) { + GetAxisGeometry(graphPtr, axisPtr); + } + if ((axisPtr->titleAlternate) && (l < axisPtr->titleWidth)) { + l = axisPtr->titleWidth; + } + if (isHoriz) { + h += axisPtr->height; + } else { + w += axisPtr->width; + } + if (axisPtr->maxTickWidth > marginPtr->maxTickWidth) { + marginPtr->maxTickWidth = axisPtr->maxTickWidth; + } + if (axisPtr->maxTickHeight > marginPtr->maxTickHeight) { + marginPtr->maxTickHeight = axisPtr->maxTickHeight; + } + } + } + } + /* Enforce a minimum size for margins. */ + if (w < 3) { + w = 3; + } + if (h < 3) { + h = 3; + } + marginPtr->nAxes = nVisible; + marginPtr->axesTitleLength = l; + marginPtr->width = w; + marginPtr->height = h; + marginPtr->axesOffset = (isHoriz) ? h : w; + return marginPtr->axesOffset; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_LayoutGraph -- + * + * Calculate the layout of the graph. Based upon the data, axis limits, + * X and Y titles, and title height, determine the cavity left which is + * the plotting surface. The first step get the data and axis limits for + * calculating the space needed for the top, bottom, left, and right + * margins. + * + * 1) The LEFT margin is the area from the left border to the Y axis + * (not including ticks). It composes the border width, the width an + * optional Y axis label and its padding, and the tick numeric labels. + * The Y axis label is rotated 90 degrees so that the width is the + * font height. + * + * 2) The RIGHT margin is the area from the end of the graph + * to the right window border. It composes the border width, + * some padding, the font height (this may be dubious. It + * appears to provide a more even border), the max of the + * legend width and 1/2 max X tick number. This last part is + * so that the last tick label is not clipped. + * + * Window Width + * ___________________________________________________________ + * | | | | + * | | TOP height of title | | + * | | | | + * | | x2 title | | + * | | | | + * | | height of x2-axis | | + * |__________|_______________________________|_______________| W + * | | -plotpady | | i + * |__________|_______________________________|_______________| n + * | | top right | | d + * | | | | o + * | LEFT | | RIGHT | w + * | | | | + * | y | Free area = 104% | y2 | H + * | | Plotting surface = 100% | | e + * | t | Tick length = 2 + 2% | t | i + * | i | | i | g + * | t | | t legend| h + * | l | | l width| t + * | e | | e | + * | height| |height | + * | of | | of | + * | y-axis| |y2-axis | + * | | | | + * | |origin 0,0 | | + * |__________|_left_________________bottom___|_______________| + * | |-plotpady | | + * |__________|_______________________________|_______________| + * | | (xoffset, yoffset) | | + * | | | | + * | | height of x-axis | | + * | | | | + * | | BOTTOM x title | | + * |__________|_______________________________|_______________| + * + * 3) The TOP margin is the area from the top window border to the top + * of the graph. It composes the border width, twice the height of + * the title font (if one is given) and some padding between the + * title. + * + * 4) The BOTTOM margin is area from the bottom window border to the + * X axis (not including ticks). It composes the border width, the height + * an optional X axis label and its padding, the height of the font + * of the tick labels. + * + * The plotting area is between the margins which includes the X and Y axes + * including the ticks but not the tick numeric labels. The length of the + * ticks and its padding is 5% of the entire plotting area. Hence the entire + * plotting area is scaled as 105% of the width and height of the area. + * + * The axis labels, ticks labels, title, and legend may or may not be + * displayed which must be taken into account. + * + * if reqWidth > 0 : set outer size + * if reqPlotWidth > 0 : set plot size + *--------------------------------------------------------------------------- + */ +void +Blt_LayoutGraph(Graph *graphPtr) +{ + unsigned int titleY; + unsigned int left, right, top, bottom; + unsigned int plotWidth, plotHeight; + unsigned int inset, inset2; + int width, height; + int pad; + + width = graphPtr->width; + height = graphPtr->height; + + /* + * Step 1: Compute the amount of space needed to display the axes + * associated with each margin. They can be overridden by + * -leftmargin, -rightmargin, -bottommargin, and -topmargin + * graph options, respectively. + */ + left = GetMarginGeometry(graphPtr, &graphPtr->leftMargin); + right = GetMarginGeometry(graphPtr, &graphPtr->rightMargin); + top = GetMarginGeometry(graphPtr, &graphPtr->topMargin); + bottom = GetMarginGeometry(graphPtr, &graphPtr->bottomMargin); + + pad = graphPtr->bottomMargin.maxTickWidth; + if (pad < graphPtr->topMargin.maxTickWidth) { + pad = graphPtr->topMargin.maxTickWidth; + } + pad = pad / 2 + 3; + if (right < pad) { + right = pad; + } + if (left < pad) { + left = pad; + } + pad = graphPtr->leftMargin.maxTickHeight; + if (pad < graphPtr->rightMargin.maxTickHeight) { + pad = graphPtr->rightMargin.maxTickHeight; + } + pad = pad / 2; + if (top < pad) { + top = pad; + } + if (bottom < pad) { + bottom = pad; + } + + if (graphPtr->leftMargin.reqSize > 0) { + left = graphPtr->leftMargin.reqSize; + } + if (graphPtr->rightMargin.reqSize > 0) { + right = graphPtr->rightMargin.reqSize; + } + if (graphPtr->topMargin.reqSize > 0) { + top = graphPtr->topMargin.reqSize; + } + if (graphPtr->bottomMargin.reqSize > 0) { + bottom = graphPtr->bottomMargin.reqSize; + } + + /* + * Step 2: Add the graph title height to the top margin. + */ + if (graphPtr->title != NULL) { + top += graphPtr->titleHeight + 6; + } + inset = (graphPtr->inset + graphPtr->plotBW); + inset2 = 2 * inset; + + /* + * Step 3: Estimate the size of the plot area from the remaining + * space. This may be overridden by the -plotwidth and + * -plotheight graph options. We use this to compute the + * size of the legend. + */ + if (width == 0) { + width = 400; + } + if (height == 0) { + height = 400; + } + plotWidth = (graphPtr->reqPlotWidth > 0) ? graphPtr->reqPlotWidth : + width - (inset2 + left + right); /* Plot width. */ + plotHeight = (graphPtr->reqPlotHeight > 0) ? graphPtr->reqPlotHeight : + height - (inset2 + top + bottom); /* Plot height. */ + Blt_MapLegend(graphPtr, plotWidth, plotHeight); + + /* + * Step 2: Add the legend to the appropiate margin. + */ + if (!Blt_Legend_IsHidden(graphPtr)) { + switch (Blt_Legend_Site(graphPtr)) { + case LEGEND_RIGHT: + right += Blt_Legend_Width(graphPtr) + 2; + break; + case LEGEND_LEFT: + left += Blt_Legend_Width(graphPtr) + 2; + break; + case LEGEND_TOP: + top += Blt_Legend_Height(graphPtr) + 2; + break; + case LEGEND_BOTTOM: + bottom += Blt_Legend_Height(graphPtr) + 2; + break; + case LEGEND_XY: + case LEGEND_PLOT: + case LEGEND_WINDOW: + /* Do nothing. */ + break; + } + } + + /* + * Recompute the plotarea or graph size, now accounting for the legend. + */ + if (graphPtr->reqPlotWidth == 0) { + plotWidth = width - (inset2 + left + right); + if (plotWidth < 1) { + plotWidth = 1; + } + } + if (graphPtr->reqPlotHeight == 0) { + plotHeight = height - (inset2 + top + bottom); + if (plotHeight < 1) { + plotHeight = 1; + } + } + + /* + * Step 5: If necessary, correct for the requested plot area aspect + * ratio. + */ + if ((graphPtr->reqPlotWidth == 0) && (graphPtr->reqPlotHeight == 0) && + (graphPtr->aspect > 0.0f)) { + float ratio; + + /* + * Shrink one dimension of the plotarea to fit the requested + * width/height aspect ratio. + */ + ratio = (float)plotWidth / (float)plotHeight; + if (ratio > graphPtr->aspect) { + int scaledWidth; + + /* Shrink the width. */ + scaledWidth = (int)(plotHeight * graphPtr->aspect); + if (scaledWidth < 1) { + scaledWidth = 1; + } + /* Add the difference to the right margin. */ + /* CHECK THIS: w = scaledWidth; */ + right += (plotWidth - scaledWidth); + } else { + int scaledHeight; + + /* Shrink the height. */ + scaledHeight = (int)(plotWidth / graphPtr->aspect); + if (scaledHeight < 1) { + scaledHeight = 1; + } + /* Add the difference to the top margin. */ + /* CHECK THIS: h = scaledHeight; */ + top += (plotHeight - scaledHeight); + } + } + + /* + * Step 6: If there's multiple axes in a margin, the axis titles will be + * displayed in the adjoining margins. Make sure there's room + * for the longest axis titles. + */ + + if (top < graphPtr->leftMargin.axesTitleLength) { + top = graphPtr->leftMargin.axesTitleLength; + } + if (right < graphPtr->bottomMargin.axesTitleLength) { + right = graphPtr->bottomMargin.axesTitleLength; + } + if (top < graphPtr->rightMargin.axesTitleLength) { + top = graphPtr->rightMargin.axesTitleLength; + } + if (right < graphPtr->topMargin.axesTitleLength) { + right = graphPtr->topMargin.axesTitleLength; + } + + /* + * Step 7: Override calculated values with requested margin sizes. + */ + if (graphPtr->leftMargin.reqSize > 0) { + left = graphPtr->leftMargin.reqSize; + } + if (graphPtr->rightMargin.reqSize > 0) { + right = graphPtr->rightMargin.reqSize; + } + if (graphPtr->topMargin.reqSize > 0) { + top = graphPtr->topMargin.reqSize; + } + if (graphPtr->bottomMargin.reqSize > 0) { + bottom = graphPtr->bottomMargin.reqSize; + } + if (graphPtr->reqPlotWidth > 0) { + int w; + + /* + * Width of plotarea is constained. If there's extra space, add it to + * th left and/or right margins. If there's too little, grow the + * graph width to accomodate it. + */ + w = plotWidth + inset2 + left + right; + if (width > w) { /* Extra space in window. */ + int extra; + + extra = (width - w) / 2; + if (graphPtr->leftMargin.reqSize == 0) { + left += extra; + if (graphPtr->rightMargin.reqSize == 0) { + right += extra; + } else { + left += extra; + } + } else if (graphPtr->rightMargin.reqSize == 0) { + right += extra + extra; + } + } else if (width < w) { + width = w; + } + } + if (graphPtr->reqPlotHeight > 0) { /* Constrain the plotarea height. */ + int h; + + /* + * Height of plotarea is constained. If there's extra space, + * add it to th top and/or bottom margins. If there's too little, + * grow the graph height to accomodate it. + */ + h = plotHeight + inset2 + top + bottom; + if (height > h) { /* Extra space in window. */ + int extra; + + extra = (height - h) / 2; + if (graphPtr->topMargin.reqSize == 0) { + top += extra; + if (graphPtr->bottomMargin.reqSize == 0) { + bottom += extra; + } else { + top += extra; + } + } else if (graphPtr->bottomMargin.reqSize == 0) { + bottom += extra + extra; + } + } else if (height < h) { + height = h; + } + } + graphPtr->width = width; + graphPtr->height = height; + graphPtr->left = left + inset; + graphPtr->top = top + inset; + graphPtr->right = width - right - inset; + graphPtr->bottom = height - bottom - inset; + + graphPtr->leftMargin.width = left + graphPtr->inset; + graphPtr->rightMargin.width = right + graphPtr->inset; + graphPtr->topMargin.height = top + graphPtr->inset; + graphPtr->bottomMargin.height = bottom + graphPtr->inset; + + graphPtr->vOffset = graphPtr->top + graphPtr->padTop; + graphPtr->vRange = plotHeight - PADDING(graphPtr->yPad); + graphPtr->hOffset = graphPtr->left + graphPtr->padLeft; + graphPtr->hRange = plotWidth - PADDING(graphPtr->xPad); + + if (graphPtr->vRange < 1) { + graphPtr->vRange = 1; + } + if (graphPtr->hRange < 1) { + graphPtr->hRange = 1; + } + graphPtr->hScale = 1.0f / (float)graphPtr->hRange; + graphPtr->vScale = 1.0f / (float)graphPtr->vRange; + + /* + * Calculate the placement of the graph title so it is centered within the + * space provided for it in the top margin + */ + titleY = graphPtr->titleHeight; + graphPtr->titleY = 3 + graphPtr->inset; + graphPtr->titleX = (graphPtr->right + graphPtr->left) / 2; + +} + +/* + *--------------------------------------------------------------------------- + * + * ConfigureAxis -- + * + * Configures axis attributes (font, line width, label, etc). + * + * Results: + * The return value is a standard TCL result. + * + * Side Effects: + * Axis layout is deferred until the height and width of the window are + * known. + * + *--------------------------------------------------------------------------- + */ +static int +ConfigureAxis(Axis *axisPtr) +{ + Graph *graphPtr = axisPtr->obj.graphPtr; + float angle; + + /* Check the requested axis limits. Can't allow -min to be greater than + * -max. Do this regardless of -checklimits option. We want to always + * detect when the user has zoomed in beyond the precision of the data.*/ + if (((DEFINED(axisPtr->reqMin)) && (DEFINED(axisPtr->reqMax))) && + (axisPtr->reqMin >= axisPtr->reqMax)) { + char msg[200]; + sprintf_s(msg, 200, + "impossible axis limits (-min %g >= -max %g) for \"%s\"", + axisPtr->reqMin, axisPtr->reqMax, axisPtr->obj.name); + Tcl_AppendResult(graphPtr->interp, msg, (char *)NULL); + return TCL_ERROR; + } + axisPtr->scrollMin = axisPtr->reqScrollMin; + axisPtr->scrollMax = axisPtr->reqScrollMax; + if (axisPtr->logScale) { + if (axisPtr->flags & AXIS_CHECK_LIMITS) { + /* Check that the logscale limits are positive. */ + if ((DEFINED(axisPtr->reqMin)) && (axisPtr->reqMin <= 0.0)) { + Tcl_AppendResult(graphPtr->interp,"bad logscale -min limit \"", + Blt_Dtoa(graphPtr->interp, axisPtr->reqMin), + "\" for axis \"", axisPtr->obj.name, "\"", + (char *)NULL); + return TCL_ERROR; + } + } + if ((DEFINED(axisPtr->scrollMin)) && (axisPtr->scrollMin <= 0.0)) { + axisPtr->scrollMin = Blt_NaN(); + } + if ((DEFINED(axisPtr->scrollMax)) && (axisPtr->scrollMax <= 0.0)) { + axisPtr->scrollMax = Blt_NaN(); + } + } + angle = FMOD(axisPtr->tickAngle, 360.0); + if (angle < 0.0f) { + angle += 360.0f; + } + if (axisPtr->normalBg != NULL) { + Blt_SetBackgroundChangedProc(axisPtr->normalBg, Blt_UpdateGraph, + graphPtr); + } + if (axisPtr->activeBg != NULL) { + Blt_SetBackgroundChangedProc(axisPtr->activeBg, Blt_UpdateGraph, + graphPtr); + } + axisPtr->tickAngle = angle; + ResetTextStyles(axisPtr); + + axisPtr->titleWidth = axisPtr->titleHeight = 0; + if (axisPtr->title != NULL) { + unsigned int w, h; + + Blt_GetTextExtents(axisPtr->titleFont, 0, axisPtr->title, -1, &w, &h); + axisPtr->titleWidth = (unsigned short int)w; + axisPtr->titleHeight = (unsigned short int)h; + } + + /* + * Don't bother to check what configuration options have changed. Almost + * every option changes the size of the plotting area (except for -color + * and -titlecolor), requiring the graph and its contents to be completely + * redrawn. + * + * Recompute the scale and offset of the axis in case -min, -max options + * have changed. + */ + graphPtr->flags |= REDRAW_WORLD; + graphPtr->flags |= MAP_WORLD | RESET_AXES | CACHE_DIRTY; + axisPtr->flags |= DIRTY; + Blt_EventuallyRedrawGraph(graphPtr); + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * NewAxis -- + * + * Create and initialize a structure containing information to display + * a graph axis. + * + * Results: + * The return value is a pointer to an Axis structure. + * + *--------------------------------------------------------------------------- + */ +static Axis * +NewAxis(Graph *graphPtr, const char *name, int margin) +{ + Axis *axisPtr; + Blt_HashEntry *hPtr; + int isNew; + + if (name[0] == '-') { + Tcl_AppendResult(graphPtr->interp, "name of axis \"", name, + "\" can't start with a '-'", (char *)NULL); + return NULL; + } + hPtr = Blt_CreateHashEntry(&graphPtr->axes.table, name, &isNew); + if (!isNew) { + axisPtr = Blt_GetHashValue(hPtr); + if ((axisPtr->flags & DELETE_PENDING) == 0) { + Tcl_AppendResult(graphPtr->interp, "axis \"", name, + "\" already exists in \"", Tk_PathName(graphPtr->tkwin), "\"", + (char *)NULL); + return NULL; + } + axisPtr->flags &= ~DELETE_PENDING; + } else { + axisPtr = Blt_Calloc(1, sizeof(Axis)); + if (axisPtr == NULL) { + Tcl_AppendResult(graphPtr->interp, + "can't allocate memory for axis \"", name, "\"", (char *)NULL); + return NULL; + } + axisPtr->obj.name = Blt_AssertStrdup(name); + axisPtr->hashPtr = hPtr; + Blt_GraphSetObjectClass(&axisPtr->obj, CID_NONE); + axisPtr->obj.graphPtr = graphPtr; + axisPtr->looseMin = axisPtr->looseMax = AXIS_TIGHT; + axisPtr->reqNumMinorTicks = 2; + axisPtr->reqNumMajorTicks = 4 /*10*/; + axisPtr->margin = MARGIN_NONE; + axisPtr->tickLength = 8; + axisPtr->scrollUnits = 10; + axisPtr->reqMin = axisPtr->reqMax = Blt_NaN(); + axisPtr->reqScrollMin = axisPtr->reqScrollMax = Blt_NaN(); + axisPtr->flags = (AXIS_SHOWTICKS|AXIS_GRIDMINOR|AXIS_AUTO_MAJOR| + AXIS_AUTO_MINOR | AXIS_EXTERIOR); + if (graphPtr->classId == CID_ELEM_BAR) { + axisPtr->flags |= AXIS_GRID; + } + if ((graphPtr->classId == CID_ELEM_BAR) && + ((margin == MARGIN_TOP) || (margin == MARGIN_BOTTOM))) { + axisPtr->reqStep = 1.0; + axisPtr->reqNumMinorTicks = 0; + } + if ((margin == MARGIN_RIGHT) || (margin == MARGIN_TOP)) { + axisPtr->flags |= HIDE; + } + Blt_Ts_InitStyle(axisPtr->limitsTextStyle); + axisPtr->tickLabels = Blt_Chain_Create(); + axisPtr->lineWidth = 1; + Blt_SetHashValue(hPtr, axisPtr); + } + return axisPtr; +} + +static int +GetAxisFromObj(Tcl_Interp *interp, Graph *graphPtr, Tcl_Obj *objPtr, + Axis **axisPtrPtr) +{ + Blt_HashEntry *hPtr; + const char *name; + + *axisPtrPtr = NULL; + name = Tcl_GetString(objPtr); + hPtr = Blt_FindHashEntry(&graphPtr->axes.table, name); + if (hPtr != NULL) { + Axis *axisPtr; + + axisPtr = Blt_GetHashValue(hPtr); + if ((axisPtr->flags & DELETE_PENDING) == 0) { + *axisPtrPtr = axisPtr; + return TCL_OK; + } + } + if (interp != NULL) { + Tcl_AppendResult(interp, "can't find axis \"", name, "\" in \"", + Tk_PathName(graphPtr->tkwin), "\"", (char *)NULL); + } + return TCL_ERROR; +} + +static int +GetAxisByClass(Tcl_Interp *interp, Graph *graphPtr, Tcl_Obj *objPtr, + ClassId classId, Axis **axisPtrPtr) +{ + Axis *axisPtr; + + if (GetAxisFromObj(interp, graphPtr, objPtr, &axisPtr) != TCL_OK) { + return TCL_ERROR; + } + if (classId != CID_NONE) { + if ((axisPtr->refCount == 0) || (axisPtr->obj.classId == CID_NONE)) { + /* Set the axis type on the first use of it. */ + Blt_GraphSetObjectClass(&axisPtr->obj, classId); + } else if (axisPtr->obj.classId != classId) { + if (interp != NULL) { + Tcl_AppendResult(interp, "axis \"", Tcl_GetString(objPtr), + "\" is already in use on an opposite ", + axisPtr->obj.className, "-axis", + (char *)NULL); + } + return TCL_ERROR; + } + axisPtr->refCount++; + } + *axisPtrPtr = axisPtr; + return TCL_OK; +} + +void +Blt_DestroyAxes(Graph *graphPtr) +{ + { + Blt_HashEntry *hPtr; + Blt_HashSearch cursor; + + for (hPtr = Blt_FirstHashEntry(&graphPtr->axes.table, &cursor); + hPtr != NULL; hPtr = Blt_NextHashEntry(&cursor)) { + Axis *axisPtr; + + axisPtr = Blt_GetHashValue(hPtr); + axisPtr->hashPtr = NULL; + DestroyAxis(axisPtr); + } + } + Blt_DeleteHashTable(&graphPtr->axes.table); + { + int i; + + for (i = 0; i < 4; i++) { + Blt_Chain_Destroy(graphPtr->axisChain[i]); + } + } + Blt_DeleteHashTable(&graphPtr->axes.tagTable); + Blt_Chain_Destroy(graphPtr->axes.displayList); +} + +void +Blt_ConfigureAxes(Graph *graphPtr) +{ + Blt_HashEntry *hPtr; + Blt_HashSearch cursor; + + for (hPtr = Blt_FirstHashEntry(&graphPtr->axes.table, &cursor); + hPtr != NULL; hPtr = Blt_NextHashEntry(&cursor)) { + Axis *axisPtr; + + axisPtr = Blt_GetHashValue(hPtr); + ConfigureAxis(axisPtr); + } +} + +int +Blt_DefaultAxes(Graph *graphPtr) +{ + int i; + int flags; + + flags = Blt_GraphType(graphPtr); + for (i = 0; i < 4; i++) { + Blt_Chain chain; + Axis *axisPtr; + + chain = Blt_Chain_Create(); + graphPtr->axisChain[i] = chain; + + /* Create a default axis for each chain. */ + axisPtr = NewAxis(graphPtr, axisNames[i].name, i); + if (axisPtr == NULL) { + return TCL_ERROR; + } + axisPtr->refCount = 1; /* Default axes are assumed in use. */ + axisPtr->margin = i; + axisPtr->flags |= AXIS_USE; + Blt_GraphSetObjectClass(&axisPtr->obj, axisNames[i].classId); + /* + * Blt_ConfigureComponentFromObj creates a temporary child window + * by the name of the axis. It's used so that the Tk routines + * that access the X resource database can describe a single + * component and not the entire graph. + */ + if (Blt_ConfigureComponentFromObj(graphPtr->interp, graphPtr->tkwin, + axisPtr->obj.name, "Axis", configSpecs, 0, (Tcl_Obj **)NULL, + (char *)axisPtr, flags) != TCL_OK) { + return TCL_ERROR; + } + if (ConfigureAxis(axisPtr) != TCL_OK) { + return TCL_ERROR; + } + axisPtr->link = Blt_Chain_Append(chain, axisPtr); + axisPtr->chain = chain; + } + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * ActivateOp -- + * + * Activates the axis, drawing the axis with its -activeforeground, + * -activebackgound, -activerelief attributes. + * + * Results: + * A standard TCL result. + * + * Side Effects: + * Graph will be redrawn to reflect the new axis attributes. + * + *--------------------------------------------------------------------------- + */ +static int +ActivateOp(Tcl_Interp *interp, Axis *axisPtr, int objc, Tcl_Obj *const *objv) +{ + Graph *graphPtr = axisPtr->obj.graphPtr; + const char *string; + + string = Tcl_GetString(objv[2]); + if (string[0] == 'a') { + axisPtr->flags |= ACTIVE; + } else { + axisPtr->flags &= ~ACTIVE; + } + if ((axisPtr->flags & (AXIS_USE|HIDE)) == AXIS_USE) { + graphPtr->flags |= DRAW_MARGINS | CACHE_DIRTY; + Blt_EventuallyRedrawGraph(graphPtr); + } + return TCL_OK; +} + +/*------------------------------------------------------------------------------- + * + * BindOp -- + * + * .g axis bind axisName sequence command + * + *--------------------------------------------------------------------------- + */ +static int +BindOp(Tcl_Interp *interp, Axis *axisPtr, int objc, Tcl_Obj *const *objv) +{ + Graph *graphPtr = axisPtr->obj.graphPtr; + + return Blt_ConfigureBindingsFromObj(interp, graphPtr->bindTable, + Blt_MakeAxisTag(graphPtr, axisPtr->obj.name), objc, objv); +} + +/* + *--------------------------------------------------------------------------- + * + * CgetOp -- + * + * Queries axis attributes (font, line width, label, etc). + * + * Results: + * Return value is a standard TCL result. If querying configuration + * values, interp->result will contain the results. + * + *--------------------------------------------------------------------------- + */ +/* ARGSUSED */ +static int +CgetOp(Tcl_Interp *interp, Axis *axisPtr, int objc, Tcl_Obj *const *objv) +{ + Graph *graphPtr = axisPtr->obj.graphPtr; + + return Blt_ConfigureValueFromObj(interp, graphPtr->tkwin, configSpecs, + (char *)axisPtr, objv[0], Blt_GraphType(graphPtr)); +} + +/* + *--------------------------------------------------------------------------- + * + * ConfigureOp -- + * + * Queries or resets axis attributes (font, line width, label, etc). + * + * Results: + * Return value is a standard TCL result. If querying configuration + * values, interp->result will contain the results. + * + * Side Effects: + * Axis resources are possibly allocated (GC, font). Axis layout is + * deferred until the height and width of the window are known. + * + *--------------------------------------------------------------------------- + */ +static int +ConfigureOp(Tcl_Interp *interp, Axis *axisPtr, int objc, Tcl_Obj *const *objv) +{ + Graph *graphPtr = axisPtr->obj.graphPtr; + int flags; + + flags = BLT_CONFIG_OBJV_ONLY | Blt_GraphType(graphPtr); + if (objc == 0) { + return Blt_ConfigureInfoFromObj(interp, graphPtr->tkwin, configSpecs, + (char *)axisPtr, (Tcl_Obj *)NULL, flags); + } else if (objc == 1) { + return Blt_ConfigureInfoFromObj(interp, graphPtr->tkwin, configSpecs, + (char *)axisPtr, objv[0], flags); + } + if (Blt_ConfigureWidgetFromObj(interp, graphPtr->tkwin, configSpecs, + objc, objv, (char *)axisPtr, flags) != TCL_OK) { + return TCL_ERROR; + } + if (ConfigureAxis(axisPtr) != TCL_OK) { + return TCL_ERROR; + } + if (axisPtr->flags & AXIS_USE) { + if (!Blt_ConfigModified(configSpecs, "-*color", "-background", "-bg", + (char *)NULL)) { + graphPtr->flags |= CACHE_DIRTY; + } + Blt_EventuallyRedrawGraph(graphPtr); + } + return TCL_OK; +} + + +/* + *--------------------------------------------------------------------------- + * + * LimitsOp -- + * + * This procedure returns a string representing the axis limits + * of the graph. The format of the string is { left top right bottom}. + * + * Results: + * Always returns TCL_OK. The interp->result field is + * a list of the graph axis limits. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +LimitsOp(Tcl_Interp *interp, Axis *axisPtr, int objc, Tcl_Obj *const *objv) +{ + Graph *graphPtr = axisPtr->obj.graphPtr; + Tcl_Obj *listObjPtr; + double min, max; + + if (graphPtr->flags & RESET_AXES) { + Blt_ResetAxes(graphPtr); + } + if (axisPtr->logScale) { + min = EXP10(axisPtr->axisRange.min); + max = EXP10(axisPtr->axisRange.max); + } else { + min = axisPtr->axisRange.min; + max = axisPtr->axisRange.max; + } + listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL); + Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewDoubleObj(min)); + Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewDoubleObj(max)); + Tcl_SetObjResult(interp, listObjPtr); + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * InvTransformOp -- + * + * Maps the given window coordinate into an axis-value. + * + * Results: + * Returns a standard TCL result. interp->result contains + * the axis value. If an error occurred, TCL_ERROR is returned + * and interp->result will contain an error message. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +InvTransformOp(Tcl_Interp *interp, Axis *axisPtr, int objc, + Tcl_Obj *const *objv) +{ + Graph *graphPtr = axisPtr->obj.graphPtr; + double y; /* Real graph coordinate */ + int sy; /* Integer window coordinate*/ + + if (graphPtr->flags & RESET_AXES) { + Blt_ResetAxes(graphPtr); + } + if (Tcl_GetIntFromObj(interp, objv[0], &sy) != TCL_OK) { + return TCL_ERROR; + } + /* + * Is the axis vertical or horizontal? + * + * Check the site where the axis was positioned. If the axis is + * virtual, all we have to go on is how it was mapped to an + * element (using either -mapx or -mapy options). + */ + if (AxisIsHorizontal(axisPtr)) { + y = Blt_InvHMap(axisPtr, (double)sy); + } else { + y = Blt_InvVMap(axisPtr, (double)sy); + } + Tcl_SetDoubleObj(Tcl_GetObjResult(interp), y); + return TCL_OK; +} + + +/* + *--------------------------------------------------------------------------- + * + * MarginOp -- + * + * This procedure returns a string representing the margin the axis + * resides. The format of the string is { left top right bottom}. + * + * Results: + * Always returns TCL_OK. The interp->result field is + * a list of the graph axis limits. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +MarginOp(Tcl_Interp *interp, Axis *axisPtr, int objc, Tcl_Obj *const *objv) +{ + const char *marginName; + + marginName = ""; + if (axisPtr->flags & AXIS_USE) { + marginName = axisNames[axisPtr->margin].name; + } + Tcl_SetStringObj(Tcl_GetObjResult(interp), marginName, -1); + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * TransformOp -- + * + * Maps the given axis-value to a window coordinate. + * + * Results: + * Returns a standard TCL result. interp->result contains + * the window coordinate. If an error occurred, TCL_ERROR + * is returned and interp->result will contain an error + * message. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +TransformOp(Tcl_Interp *interp, Axis *axisPtr, int objc, Tcl_Obj *const *objv) +{ + Graph *graphPtr = axisPtr->obj.graphPtr; + double x; + + if (graphPtr->flags & RESET_AXES) { + Blt_ResetAxes(graphPtr); + } + if (Blt_ExprDoubleFromObj(interp, objv[0], &x) != TCL_OK) { + return TCL_ERROR; + } + if (AxisIsHorizontal(axisPtr)) { + x = Blt_HMap(axisPtr, x); + } else { + x = Blt_VMap(axisPtr, x); + } + Tcl_SetIntObj(Tcl_GetObjResult(interp), (int)x); + return TCL_OK; +} + + +/* + *--------------------------------------------------------------------------- + * + * TypeOp -- + * + * This procedure returns a string representing the margin the axis + * resides. The format of the string is "x", "y", or "". + * + * Results: + * Always returns TCL_OK. The interp->result field is the type of + * axis. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +TypeOp(Tcl_Interp *interp, Axis *axisPtr, int objc, Tcl_Obj *const *objv) +{ + const char *typeName; + + typeName = ""; + if (axisPtr->flags & AXIS_USE) { + if (axisNames[axisPtr->margin].classId == CID_AXIS_X) { + typeName = "x"; + } else if (axisNames[axisPtr->margin].classId == CID_AXIS_Y) { + typeName = "y"; + } + } + Tcl_SetStringObj(Tcl_GetObjResult(interp), typeName, -1); + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * UseOp -- + * + * Sets the default axis for a margin. + * + * Results: + * A standard TCL result. If the named axis doesn't exist + * an error message is put in interp->result. + * + * .g xaxis use "abc def gah" + * .g xaxis use [lappend abc [.g axis use]] + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +UseOp(Tcl_Interp *interp, Axis *axisPtr, int objc, Tcl_Obj *const *objv) +{ + Graph *graphPtr = (Graph *)axisPtr; + Blt_Chain chain; + Blt_ChainLink link; + Tcl_Obj **axisObjv; + ClassId classId; + int axisObjc; + int i; + + chain = graphPtr->margins[lastMargin].axes; + if (objc == 0) { + Tcl_Obj *listObjPtr; + + listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL); + for (link = Blt_Chain_FirstLink(chain); link != NULL; + link = Blt_Chain_NextLink(link)) { + Axis *axisPtr; + + axisPtr = Blt_Chain_GetValue(link); + Tcl_ListObjAppendElement(interp, listObjPtr, + Tcl_NewStringObj(axisPtr->obj.name, -1)); + } + Tcl_SetObjResult(interp, listObjPtr); + return TCL_OK; + } + if ((lastMargin == MARGIN_BOTTOM) || (lastMargin == MARGIN_TOP)) { + classId = (graphPtr->inverted) ? CID_AXIS_Y : CID_AXIS_X; + } else { + classId = (graphPtr->inverted) ? CID_AXIS_X : CID_AXIS_Y; + } + if (Tcl_ListObjGetElements(interp, objv[0], &axisObjc, &axisObjv) + != TCL_OK) { + return TCL_ERROR; + } + for (link = Blt_Chain_FirstLink(chain); link!= NULL; + link = Blt_Chain_NextLink(link)) { + Axis *axisPtr; + + axisPtr = Blt_Chain_GetValue(link); + axisPtr->link = NULL; + axisPtr->flags &= ~AXIS_USE; + /* Clear the axis type if it's not currently used.*/ + if (axisPtr->refCount == 0) { + Blt_GraphSetObjectClass(&axisPtr->obj, CID_NONE); + } + } + Blt_Chain_Reset(chain); + for (i = 0; i < axisObjc; i++) { + Axis *axisPtr; + + if (GetAxisFromObj(interp, graphPtr, axisObjv[i], &axisPtr) != TCL_OK){ + return TCL_ERROR; + } + if (axisPtr->obj.classId == CID_NONE) { + Blt_GraphSetObjectClass(&axisPtr->obj, classId); + } else if (axisPtr->obj.classId != classId) { + Tcl_AppendResult(interp, "wrong type axis \"", + axisPtr->obj.name, "\": can't use ", + axisPtr->obj.className, " type axis.", (char *)NULL); + return TCL_ERROR; + } + if (axisPtr->link != NULL) { + /* Move the axis from the old margin's "use" list to the new. */ + Blt_Chain_UnlinkLink(axisPtr->chain, axisPtr->link); + Blt_Chain_AppendLink(chain, axisPtr->link); + } else { + axisPtr->link = Blt_Chain_Append(chain, axisPtr); + } + axisPtr->chain = chain; + axisPtr->flags |= AXIS_USE; + } + graphPtr->flags |= (GET_AXIS_GEOMETRY | LAYOUT_NEEDED | RESET_AXES); + /* When any axis changes, we need to layout the entire graph. */ + graphPtr->flags |= (MAP_WORLD | REDRAW_WORLD); + Blt_EventuallyRedrawGraph(graphPtr); + return TCL_OK; +} + +static int +ViewOp(Tcl_Interp *interp, Axis *axisPtr, int objc, Tcl_Obj *const *objv) +{ + Graph *graphPtr; + double axisOffset, axisScale; + double fract; + double viewMin, viewMax, worldMin, worldMax; + double viewWidth, worldWidth; + + graphPtr = axisPtr->obj.graphPtr; + worldMin = axisPtr->valueRange.min; + worldMax = axisPtr->valueRange.max; + /* Override data dimensions with user-selected limits. */ + if (DEFINED(axisPtr->scrollMin)) { + worldMin = axisPtr->scrollMin; + } + if (DEFINED(axisPtr->scrollMax)) { + worldMax = axisPtr->scrollMax; + } + viewMin = axisPtr->min; + viewMax = axisPtr->max; + /* Bound the view within scroll region. */ + if (viewMin < worldMin) { + viewMin = worldMin; + } + if (viewMax > worldMax) { + viewMax = worldMax; + } + if (axisPtr->logScale) { + worldMin = log10(worldMin); + worldMax = log10(worldMax); + viewMin = log10(viewMin); + viewMax = log10(viewMax); + } + worldWidth = worldMax - worldMin; + viewWidth = viewMax - viewMin; + + /* Unlike horizontal axes, vertical axis values run opposite of the + * scrollbar first/last values. So instead of pushing the axis minimum + * around, we move the maximum instead. */ + if (AxisIsHorizontal(axisPtr) != axisPtr->descending) { + axisOffset = viewMin - worldMin; + axisScale = graphPtr->hScale; + } else { + axisOffset = worldMax - viewMax; + axisScale = graphPtr->vScale; + } + if (objc == 4) { + Tcl_Obj *listObjPtr; + double first, last; + + first = Clamp(axisOffset / worldWidth); + last = Clamp((axisOffset + viewWidth) / worldWidth); + listObjPtr = Tcl_NewListObj(0, NULL); + Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewDoubleObj(first)); + Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewDoubleObj(last)); + Tcl_SetObjResult(interp, listObjPtr); + return TCL_OK; + } + fract = axisOffset / worldWidth; + if (GetAxisScrollInfo(interp, objc, objv, &fract, + viewWidth / worldWidth, axisPtr->scrollUnits, axisScale) != TCL_OK) { + return TCL_ERROR; + } + if (AxisIsHorizontal(axisPtr) != axisPtr->descending) { + axisPtr->reqMin = (fract * worldWidth) + worldMin; + axisPtr->reqMax = axisPtr->reqMin + viewWidth; + } else { + axisPtr->reqMax = worldMax - (fract * worldWidth); + axisPtr->reqMin = axisPtr->reqMax - viewWidth; + } + if (axisPtr->logScale) { + axisPtr->reqMin = EXP10(axisPtr->reqMin); + axisPtr->reqMax = EXP10(axisPtr->reqMax); + } + graphPtr->flags |= (GET_AXIS_GEOMETRY | LAYOUT_NEEDED | RESET_AXES); + Blt_EventuallyRedrawGraph(graphPtr); + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * AxisCreateOp -- + * + * Creates a new axis. + * + * Results: + * Returns a standard TCL result. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +AxisCreateOp(Tcl_Interp *interp, Graph *graphPtr, int objc, + Tcl_Obj *const *objv) +{ + Axis *axisPtr; + int flags; + + axisPtr = NewAxis(graphPtr, Tcl_GetString(objv[3]), MARGIN_NONE); + if (axisPtr == NULL) { + return TCL_ERROR; + } + flags = Blt_GraphType(graphPtr); + if ((Blt_ConfigureComponentFromObj(interp, graphPtr->tkwin, + axisPtr->obj.name, "Axis", configSpecs, objc - 4, objv + 4, + (char *)axisPtr, flags) != TCL_OK) || + (ConfigureAxis(axisPtr) != TCL_OK)) { + DestroyAxis(axisPtr); + return TCL_ERROR; + } + Tcl_SetStringObj(Tcl_GetObjResult(interp), axisPtr->obj.name, -1); + return TCL_OK; +} +/* + *--------------------------------------------------------------------------- + * + * AxisActivateOp -- + * + * Activates the axis, drawing the axis with its -activeforeground, + * -activebackgound, -activerelief attributes. + * + * Results: + * A standard TCL result. + * + * Side Effects: + * Graph will be redrawn to reflect the new axis attributes. + * + *--------------------------------------------------------------------------- + */ +static int +AxisActivateOp(Tcl_Interp *interp, Graph *graphPtr, int objc, + Tcl_Obj *const *objv) +{ + Axis *axisPtr; + + if (GetAxisFromObj(interp, graphPtr, objv[3], &axisPtr) != TCL_OK) { + return TCL_ERROR; + } + return ActivateOp(interp, axisPtr, objc, objv); +} + + +/*------------------------------------------------------------------------------- + * + * AxisBindOp -- + * + * .g axis bind axisName sequence command + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +AxisBindOp(Tcl_Interp *interp, Graph *graphPtr, int objc, + Tcl_Obj *const *objv) +{ + if (objc == 3) { + Blt_HashEntry *hPtr; + Blt_HashSearch cursor; + Tcl_Obj *listObjPtr; + + listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL); + for (hPtr = Blt_FirstHashEntry(&graphPtr->axes.tagTable, &cursor); + hPtr != NULL; hPtr = Blt_NextHashEntry(&cursor)) { + const char *tagName; + Tcl_Obj *objPtr; + + tagName = Blt_GetHashKey(&graphPtr->axes.tagTable, hPtr); + objPtr = Tcl_NewStringObj(tagName, -1); + Tcl_ListObjAppendElement(interp, listObjPtr, objPtr); + } + Tcl_SetObjResult(interp, listObjPtr); + return TCL_OK; + } + return Blt_ConfigureBindingsFromObj(interp, graphPtr->bindTable, + Blt_MakeAxisTag(graphPtr, Tcl_GetString(objv[3])), objc - 4, objv + 4); +} + + +/* + *--------------------------------------------------------------------------- + * + * AxisCgetOp -- + * + * Queries axis attributes (font, line width, label, etc). + * + * Results: + * Return value is a standard TCL result. If querying configuration + * values, interp->result will contain the results. + * + *--------------------------------------------------------------------------- + */ +/* ARGSUSED */ +static int +AxisCgetOp(Tcl_Interp *interp, Graph *graphPtr, int objc, Tcl_Obj *const *objv) +{ + Axis *axisPtr; + + if (GetAxisFromObj(interp, graphPtr, objv[3], &axisPtr) != TCL_OK) { + return TCL_ERROR; + } + return CgetOp(interp, axisPtr, objc - 4, objv + 4); +} + +/* + *--------------------------------------------------------------------------- + * + * AxisConfigureOp -- + * + * Queries or resets axis attributes (font, line width, label, etc). + * + * Results: + * Return value is a standard TCL result. If querying configuration + * values, interp->result will contain the results. + * + * Side Effects: + * Axis resources are possibly allocated (GC, font). Axis layout is + * deferred until the height and width of the window are known. + * + *--------------------------------------------------------------------------- + */ +static int +AxisConfigureOp(Tcl_Interp *interp, Graph *graphPtr, int objc, + Tcl_Obj *const *objv) +{ + Tcl_Obj *const *options; + int i; + int nNames, nOpts; + + /* Figure out where the option value pairs begin */ + objc -= 3; + objv += 3; + for (i = 0; i < objc; i++) { + Axis *axisPtr; + const char *string; + + string = Tcl_GetString(objv[i]); + if (string[0] == '-') { + break; + } + if (GetAxisFromObj(interp, graphPtr, objv[i], &axisPtr) != TCL_OK) { + return TCL_ERROR; + } + } + nNames = i; /* Number of pen names specified */ + nOpts = objc - i; /* Number of options specified */ + options = objv + i; /* Start of options in objv */ + + for (i = 0; i < nNames; i++) { + Axis *axisPtr; + + if (GetAxisFromObj(interp, graphPtr, objv[i], &axisPtr) != TCL_OK) { + return TCL_ERROR; + } + if (ConfigureOp(interp, axisPtr, nOpts, options) != TCL_OK) { + break; + } + } + if (i < nNames) { + return TCL_ERROR; + } + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * AxisDeleteOp -- + * + * Deletes one or more axes. The actual removal may be deferred until the + * axis is no longer used by any element. The axis can't be referenced by + * its name any longer and it may be recreated. + * + * Results: + * Returns a standard TCL result. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +AxisDeleteOp(Tcl_Interp *interp, Graph *graphPtr, int objc, + Tcl_Obj *const *objv) +{ + int i; + + for (i = 3; i < objc; i++) { + Axis *axisPtr; + + if (GetAxisFromObj(interp, graphPtr, objv[i], &axisPtr) != TCL_OK) { + return TCL_ERROR; + } + axisPtr->flags |= DELETE_PENDING; + if (axisPtr->refCount == 0) { + Tcl_EventuallyFree(axisPtr, FreeAxis); + } + } + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * AxisFocusOp -- + * + * Activates the axis, drawing the axis with its -activeforeground, + * -activebackgound, -activerelief attributes. + * + * Results: + * A standard TCL result. + * + * Side Effects: + * Graph will be redrawn to reflect the new axis attributes. + * + *--------------------------------------------------------------------------- + */ +static int +AxisFocusOp(Tcl_Interp *interp, Graph *graphPtr, int objc, Tcl_Obj *const *objv) +{ + if (objc > 3) { + Axis *axisPtr; + const char *string; + + axisPtr = NULL; + string = Tcl_GetString(objv[3]); + if ((string[0] != '\0') && + (GetAxisFromObj(interp, graphPtr, objv[3], &axisPtr) != TCL_OK)) { + return TCL_ERROR; + } + graphPtr->focusPtr = NULL; + if ((axisPtr != NULL) && + ((axisPtr->flags & (AXIS_USE|HIDE)) == AXIS_USE)) { + graphPtr->focusPtr = axisPtr; + } + Blt_SetFocusItem(graphPtr->bindTable, graphPtr->focusPtr, NULL); + } + /* Return the name of the axis that has focus. */ + if (graphPtr->focusPtr != NULL) { + Tcl_SetStringObj(Tcl_GetObjResult(interp), + graphPtr->focusPtr->obj.name, -1); + } + return TCL_OK; +} + + +/* + *--------------------------------------------------------------------------- + * + * AxisGetOp -- + * + * Returns the name of the picked axis (using the axis bind operation). + * Right now, the only name accepted is "current". + * + * Results: + * A standard TCL result. The interpreter result will contain the name of + * the axis. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +AxisGetOp(Tcl_Interp *interp, Graph *graphPtr, int objc, Tcl_Obj *const *objv) +{ + Axis *axisPtr; + + axisPtr = Blt_GetCurrentItem(graphPtr->bindTable); + /* Report only on axes. */ + if ((axisPtr != NULL) && + ((axisPtr->obj.classId == CID_AXIS_X) || + (axisPtr->obj.classId == CID_AXIS_Y) || + (axisPtr->obj.classId == CID_NONE))) { + char c; + char *string; + + string = Tcl_GetString(objv[3]); + c = string[0]; + if ((c == 'c') && (strcmp(string, "current") == 0)) { + Tcl_SetStringObj(Tcl_GetObjResult(interp), axisPtr->obj.name,-1); + } else if ((c == 'd') && (strcmp(string, "detail") == 0)) { + Tcl_SetStringObj(Tcl_GetObjResult(interp), axisPtr->detail, -1); + } + } + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * AxisInvTransformOp -- + * + * Maps the given window coordinate into an axis-value. + * + * Results: + * Returns a standard TCL result. interp->result contains + * the axis value. If an error occurred, TCL_ERROR is returned + * and interp->result will contain an error message. + * + *--------------------------------------------------------------------------- + */ +static int +AxisInvTransformOp(Tcl_Interp *interp, Graph *graphPtr, int objc, + Tcl_Obj *const *objv) +{ + Axis *axisPtr; + + if (GetAxisFromObj(interp, graphPtr, objv[3], &axisPtr) != TCL_OK) { + return TCL_ERROR; + } + return InvTransformOp(interp, axisPtr, objc - 4, objv + 4); +} + +/* + *--------------------------------------------------------------------------- + * + * AxisLimitsOp -- + * + * This procedure returns a string representing the axis limits of the + * graph. The format of the string is { left top right bottom}. + * + * Results: + * Always returns TCL_OK. The interp->result field is + * a list of the graph axis limits. + * + *--------------------------------------------------------------------------- + */ +static int +AxisLimitsOp(Tcl_Interp *interp, Graph *graphPtr, int objc, + Tcl_Obj *const *objv) +{ + Axis *axisPtr; + + if (GetAxisFromObj(interp, graphPtr, objv[3], &axisPtr) != TCL_OK) { + return TCL_ERROR; + } + return LimitsOp(interp, axisPtr, objc - 4, objv + 4); +} + +/* + *--------------------------------------------------------------------------- + * + * AxisMarginOp -- + * + * This procedure returns a string representing the axis limits of the + * graph. The format of the string is { left top right bottom}. + * + * Results: + * Always returns TCL_OK. The interp->result field is + * a list of the graph axis limits. + * + *--------------------------------------------------------------------------- + */ +static int +AxisMarginOp(Tcl_Interp *interp, Graph *graphPtr, int objc, + Tcl_Obj *const *objv) +{ + Axis *axisPtr; + + if (GetAxisFromObj(interp, graphPtr, objv[3], &axisPtr) != TCL_OK) { + return TCL_ERROR; + } + return MarginOp(interp, axisPtr, objc - 4, objv + 4); +} + + +/* + *--------------------------------------------------------------------------- + * + * AxisNamesOp -- + * + * Return a list of the names of all the axes. + * + * Results: + * Returns a standard TCL result. + * + *--------------------------------------------------------------------------- + */ + +/*ARGSUSED*/ +static int +AxisNamesOp(Tcl_Interp *interp, Graph *graphPtr, int objc, Tcl_Obj *const *objv) +{ + Tcl_Obj *listObjPtr; + + listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL); + if (objc == 3) { + Blt_HashEntry *hPtr; + Blt_HashSearch cursor; + + for (hPtr = Blt_FirstHashEntry(&graphPtr->axes.table, &cursor); + hPtr != NULL; hPtr = Blt_NextHashEntry(&cursor)) { + Axis *axisPtr; + + axisPtr = Blt_GetHashValue(hPtr); + if (axisPtr->flags & DELETE_PENDING) { + continue; + } + Tcl_ListObjAppendElement(interp, listObjPtr, + Tcl_NewStringObj(axisPtr->obj.name, -1)); + } + } else { + Blt_HashEntry *hPtr; + Blt_HashSearch cursor; + + for (hPtr = Blt_FirstHashEntry(&graphPtr->axes.table, &cursor); + hPtr != NULL; hPtr = Blt_NextHashEntry(&cursor)) { + Axis *axisPtr; + int i; + + axisPtr = Blt_GetHashValue(hPtr); + for (i = 3; i < objc; i++) { + const char *pattern; + + pattern = Tcl_GetString(objv[i]); + if (Tcl_StringMatch(axisPtr->obj.name, pattern)) { + Tcl_ListObjAppendElement(interp, listObjPtr, + Tcl_NewStringObj(axisPtr->obj.name, -1)); + break; + } + } + } + } + Tcl_SetObjResult(interp, listObjPtr); + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * AxisTransformOp -- + * + * Maps the given axis-value to a window coordinate. + * + * Results: + * Returns the window coordinate via interp->result. If an error occurred, + * TCL_ERROR is returned and interp->result will contain an error message. + * + *--------------------------------------------------------------------------- + */ +static int +AxisTransformOp(Tcl_Interp *interp, Graph *graphPtr, int objc, + Tcl_Obj *const *objv) +{ + Axis *axisPtr; + + if (GetAxisFromObj(interp, graphPtr, objv[3], &axisPtr) != TCL_OK) { + return TCL_ERROR; + } + return TransformOp(interp, axisPtr, objc - 4, objv + 4); +} + +/* + *--------------------------------------------------------------------------- + * + * AxisMarginOp -- + * + * This procedure returns a string representing the axis limits of the + * graph. The format of the string is { left top right bottom}. + * + * Results: + * Always returns TCL_OK. The interp->result field is + * a list of the graph axis limits. + * + *--------------------------------------------------------------------------- + */ +static int +AxisTypeOp(Tcl_Interp *interp, Graph *graphPtr, int objc, + Tcl_Obj *const *objv) +{ + Axis *axisPtr; + + if (GetAxisFromObj(interp, graphPtr, objv[3], &axisPtr) != TCL_OK) { + return TCL_ERROR; + } + return TypeOp(interp, axisPtr, objc - 4, objv + 4); +} + + +static int +AxisViewOp(Tcl_Interp *interp, Graph *graphPtr, int objc, Tcl_Obj *const *objv) +{ + Axis *axisPtr; + + if (GetAxisFromObj(interp, graphPtr, objv[3], &axisPtr) != TCL_OK) { + return TCL_ERROR; + } + return ViewOp(interp, axisPtr, objc - 4, objv + 4); +} + +static Blt_OpSpec virtAxisOps[] = { + {"activate", 1, AxisActivateOp, 4, 4, "axisName"}, + {"bind", 1, AxisBindOp, 3, 6, "axisName sequence command"}, + {"cget", 2, AxisCgetOp, 5, 5, "axisName option"}, + {"configure", 2, AxisConfigureOp, 4, 0, "axisName ?axisName?... " + "?option value?..."}, + {"create", 2, AxisCreateOp, 4, 0, "axisName ?option value?..."}, + {"deactivate", 3, AxisActivateOp, 4, 4, "axisName"}, + {"delete", 3, AxisDeleteOp, 3, 0, "?axisName?..."}, + {"focus", 1, AxisFocusOp, 3, 4, "?axisName?"}, + {"get", 1, AxisGetOp, 4, 4, "name"}, + {"invtransform", 1, AxisInvTransformOp, 5, 5, "axisName value"}, + {"limits", 1, AxisLimitsOp, 4, 4, "axisName"}, + {"margin", 1, AxisMarginOp, 4, 4, "axisName"}, + {"names", 1, AxisNamesOp, 3, 0, "?pattern?..."}, + {"transform", 2, AxisTransformOp, 5, 5, "axisName value"}, + {"type", 2, AxisTypeOp, 4, 4, "axisName"}, + {"view", 1, AxisViewOp, 4, 7, "axisName ?moveto fract? " + "?scroll number what?"}, +}; +static int nVirtAxisOps = sizeof(virtAxisOps) / sizeof(Blt_OpSpec); + +int +Blt_VirtualAxisOp(Graph *graphPtr, Tcl_Interp *interp, int objc, + Tcl_Obj *const *objv) +{ + GraphVirtualAxisProc *proc; + int result; + + proc = Blt_GetOpFromObj(interp, nVirtAxisOps, virtAxisOps, BLT_OP_ARG2, + objc, objv, 0); + if (proc == NULL) { + return TCL_ERROR; + } + result = (*proc) (interp, graphPtr, objc, objv); + return result; +} + +static Blt_OpSpec axisOps[] = { + {"activate", 1, ActivateOp, 3, 3, "",}, + {"bind", 1, BindOp, 2, 5, "sequence command",}, + {"cget", 2, CgetOp, 4, 4, "option",}, + {"configure", 2, ConfigureOp, 3, 0, "?option value?...",}, + {"deactivate", 1, ActivateOp, 3, 3, "",}, + {"invtransform", 1, InvTransformOp, 4, 4, "value",}, + {"limits", 1, LimitsOp, 3, 3, "",}, + {"transform", 1, TransformOp, 4, 4, "value",}, + {"use", 1, UseOp, 3, 4, "?axisName?",}, + {"view", 1, ViewOp, 3, 6, "?moveto fract? ",}, +}; + +static int nAxisOps = sizeof(axisOps) / sizeof(Blt_OpSpec); + +int +Blt_AxisOp(Tcl_Interp *interp, Graph *graphPtr, int margin, int objc, + Tcl_Obj *const *objv) +{ + int result; + GraphAxisProc *proc; + + proc = Blt_GetOpFromObj(interp, nAxisOps, axisOps, BLT_OP_ARG2, + objc, objv, 0); + if (proc == NULL) { + return TCL_ERROR; + } + if (proc == UseOp) { + lastMargin = margin; /* Set global variable to the margin + * in the argument list. Needed only + * for UseOp. */ + result = (*proc)(interp, (Axis *)graphPtr, objc - 3, objv + 3); + } else { + Axis *axisPtr; + + axisPtr = Blt_GetFirstAxis(graphPtr->margins[margin].axes); + if (axisPtr == NULL) { + return TCL_OK; + } + result = (*proc)(interp, axisPtr, objc - 3, objv + 3); + } + return result; +} + +void +Blt_MapAxes(Graph *graphPtr) +{ + int margin; + + for (margin = 0; margin < 4; margin++) { + Blt_Chain chain; + Blt_ChainLink link; + int count, offset; + + chain = graphPtr->margins[margin].axes; + count = offset = 0; + for (link = Blt_Chain_FirstLink(chain); link != NULL; + link = Blt_Chain_NextLink(link)) { + Axis *axisPtr; + + axisPtr = Blt_Chain_GetValue(link); + if ((axisPtr->flags & (AXIS_USE|DELETE_PENDING)) != AXIS_USE) { + continue; + } + if (graphPtr->stackAxes) { + if (axisPtr->reqNumMajorTicks <= 0) { + axisPtr->reqNumMajorTicks = 4; + } + MapStackedAxis(axisPtr, count, margin); + } else { + if (axisPtr->reqNumMajorTicks <= 0) { + axisPtr->reqNumMajorTicks = 4; + } + MapAxis(axisPtr, offset, margin); + } + if (axisPtr->flags & AXIS_GRID) { + MapGridlines(axisPtr); + } + offset += (AxisIsHorizontal(axisPtr)) + ? axisPtr->height : axisPtr->width; + count++; + } + } +} + +void +Blt_DrawAxes(Graph *graphPtr, Drawable drawable) +{ + int i; + + for (i = 0; i < 4; i++) { + Blt_ChainLink link; + + for (link = Blt_Chain_LastLink(graphPtr->margins[i].axes); + link != NULL; link = Blt_Chain_PrevLink(link)) { + Axis *axisPtr; + + axisPtr = Blt_Chain_GetValue(link); + if ((axisPtr->flags & (DELETE_PENDING|HIDE|AXIS_USE)) == AXIS_USE) { + DrawAxis(axisPtr, drawable); + } + } + } +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_DrawGrids -- + * + * Draws the grid lines associated with each axis. + * + * Results: + * None. + * + *--------------------------------------------------------------------------- + */ +void +Blt_DrawGrids(Graph *graphPtr, Drawable drawable) +{ + int i; + + for (i = 0; i < 4; i++) { + Blt_ChainLink link; + + for (link = Blt_Chain_FirstLink(graphPtr->margins[i].axes); link != NULL; + link = Blt_Chain_NextLink(link)) { + Axis *axisPtr; + + axisPtr = Blt_Chain_GetValue(link); + if (axisPtr->flags & (DELETE_PENDING|HIDE)) { + continue; + } + if ((axisPtr->flags & AXIS_USE) && (axisPtr->flags & AXIS_GRID)) { + Blt_Draw2DSegments(graphPtr->display, drawable, + axisPtr->major.gc, axisPtr->major.segments, + axisPtr->major.nUsed); + if (axisPtr->flags & AXIS_GRIDMINOR) { + Blt_Draw2DSegments(graphPtr->display, drawable, + axisPtr->minor.gc, axisPtr->minor.segments, + axisPtr->minor.nUsed); + } + } + } + } +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_GridsToPostScript -- + * + * Draws the grid lines associated with each axis. + * + * Results: + * None. + * + *--------------------------------------------------------------------------- + */ +void +Blt_GridsToPostScript(Graph *graphPtr, Blt_Ps ps) +{ + int i; + + for (i = 0; i < 4; i++) { + Blt_ChainLink link; + + for (link = Blt_Chain_FirstLink(graphPtr->margins[i].axes); link != NULL; + link = Blt_Chain_NextLink(link)) { + Axis *axisPtr; + + axisPtr = Blt_Chain_GetValue(link); + if ((axisPtr->flags & (DELETE_PENDING|HIDE|AXIS_USE|AXIS_GRID)) != + (AXIS_GRID|AXIS_USE)) { + continue; + } + Blt_Ps_Format(ps, "%% Axis %s: grid line attributes\n", + axisPtr->obj.name); + Blt_Ps_XSetLineAttributes(ps, axisPtr->major.color, + axisPtr->major.lineWidth, &axisPtr->major.dashes, CapButt, + JoinMiter); + Blt_Ps_Format(ps, "%% Axis %s: major grid line segments\n", + axisPtr->obj.name); + Blt_Ps_Draw2DSegments(ps, axisPtr->major.segments, + axisPtr->major.nUsed); + if (axisPtr->flags & AXIS_GRIDMINOR) { + Blt_Ps_XSetLineAttributes(ps, axisPtr->minor.color, + axisPtr->minor.lineWidth, &axisPtr->minor.dashes, CapButt, + JoinMiter); + Blt_Ps_Format(ps, "%% Axis %s: minor grid line segments\n", + axisPtr->obj.name); + Blt_Ps_Draw2DSegments(ps, axisPtr->minor.segments, + axisPtr->minor.nUsed); + } + } + } +} + +void +Blt_AxesToPostScript(Graph *graphPtr, Blt_Ps ps) +{ + Margin *mp, *mend; + + for (mp = graphPtr->margins, mend = mp + 4; mp < mend; mp++) { + Blt_ChainLink link; + + for (link = Blt_Chain_FirstLink(mp->axes); link != NULL; + link = Blt_Chain_NextLink(link)) { + Axis *axisPtr; + + axisPtr = Blt_Chain_GetValue(link); + if ((axisPtr->flags & (DELETE_PENDING|HIDE|AXIS_USE)) == AXIS_USE) { + AxisToPostScript(ps, axisPtr); + } + } + } +} + + +/* + *--------------------------------------------------------------------------- + * + * Blt_DrawAxisLimits -- + * + * Draws the min/max values of the axis in the plotting area. The text + * strings are formatted according to the "sprintf" format descriptors in + * the limitsFormats array. + * + * Results: + * None. + * + * Side Effects: + * Draws the numeric values of the axis limits into the outer regions of + * the plotting area. + * + *--------------------------------------------------------------------------- + */ +void +Blt_DrawAxisLimits(Graph *graphPtr, Drawable drawable) +{ + Blt_HashEntry *hPtr; + Blt_HashSearch cursor; + char minString[200], maxString[200]; + int vMin, hMin, vMax, hMax; + +#define SPACING 8 + vMin = vMax = graphPtr->left + graphPtr->padLeft + 2; + hMin = hMax = graphPtr->bottom - graphPtr->padBottom - 2; /* Offsets */ + + for (hPtr = Blt_FirstHashEntry(&graphPtr->axes.table, &cursor); + hPtr != NULL; hPtr = Blt_NextHashEntry(&cursor)) { + Axis *axisPtr; + Dim2D textDim; + const char *minFmt, *maxFmt; + char *minPtr, *maxPtr; + int isHoriz; + + axisPtr = Blt_GetHashValue(hPtr); + if (axisPtr->flags & DELETE_PENDING) { + continue; + } + if (axisPtr->nFormats == 0) { + continue; + } + isHoriz = AxisIsHorizontal(axisPtr); + minPtr = maxPtr = NULL; + minFmt = maxFmt = axisPtr->limitsFormats[0]; + if (axisPtr->nFormats > 1) { + maxFmt = axisPtr->limitsFormats[1]; + } + if (minFmt[0] != '\0') { + minPtr = minString; + sprintf_s(minString, 200, minFmt, axisPtr->axisRange.min); + } + if (maxFmt[0] != '\0') { + maxPtr = maxString; + sprintf_s(maxString, 200, maxFmt, axisPtr->axisRange.max); + } + if (axisPtr->descending) { + char *tmp; + + tmp = minPtr, minPtr = maxPtr, maxPtr = tmp; + } + if (maxPtr != NULL) { + if (isHoriz) { + Blt_Ts_SetAngle(axisPtr->limitsTextStyle, 90.0); + Blt_Ts_SetAnchor(axisPtr->limitsTextStyle, TK_ANCHOR_SE); + Blt_DrawText2(graphPtr->tkwin, drawable, maxPtr, + &axisPtr->limitsTextStyle, graphPtr->right, hMax, &textDim); + hMax -= (textDim.height + SPACING); + } else { + Blt_Ts_SetAngle(axisPtr->limitsTextStyle, 0.0); + Blt_Ts_SetAnchor(axisPtr->limitsTextStyle, TK_ANCHOR_NW); + Blt_DrawText2(graphPtr->tkwin, drawable, maxPtr, + &axisPtr->limitsTextStyle, vMax, graphPtr->top, &textDim); + vMax += (textDim.width + SPACING); + } + } + if (minPtr != NULL) { + Blt_Ts_SetAnchor(axisPtr->limitsTextStyle, TK_ANCHOR_SW); + if (isHoriz) { + Blt_Ts_SetAngle(axisPtr->limitsTextStyle, 90.0); + Blt_DrawText2(graphPtr->tkwin, drawable, minPtr, + &axisPtr->limitsTextStyle, graphPtr->left, hMin, &textDim); + hMin -= (textDim.height + SPACING); + } else { + Blt_Ts_SetAngle(axisPtr->limitsTextStyle, 0.0); + Blt_DrawText2(graphPtr->tkwin, drawable, minPtr, + &axisPtr->limitsTextStyle, vMin, graphPtr->bottom, &textDim); + vMin += (textDim.width + SPACING); + } + } + } /* Loop on axes */ +} + +void +Blt_AxisLimitsToPostScript(Graph *graphPtr, Blt_Ps ps) +{ + Blt_HashEntry *hPtr; + Blt_HashSearch cursor; + double vMin, hMin, vMax, hMax; + char string[200]; + +#define SPACING 8 + vMin = vMax = graphPtr->left + graphPtr->padLeft + 2; + hMin = hMax = graphPtr->bottom - graphPtr->padBottom - 2; /* Offsets */ + for (hPtr = Blt_FirstHashEntry(&graphPtr->axes.table, &cursor); + hPtr != NULL; hPtr = Blt_NextHashEntry(&cursor)) { + Axis *axisPtr; + const char *minFmt, *maxFmt; + unsigned int textWidth, textHeight; + + axisPtr = Blt_GetHashValue(hPtr); + if (axisPtr->flags & DELETE_PENDING) { + continue; + } + if (axisPtr->nFormats == 0) { + continue; + } + minFmt = maxFmt = axisPtr->limitsFormats[0]; + if (axisPtr->nFormats > 1) { + maxFmt = axisPtr->limitsFormats[1]; + } + if (*maxFmt != '\0') { + sprintf_s(string, 200, maxFmt, axisPtr->axisRange.max); + Blt_GetTextExtents(axisPtr->tickFont, 0, string, -1, &textWidth, + &textHeight); + if ((textWidth > 0) && (textHeight > 0)) { + if (axisPtr->obj.classId == CID_AXIS_X) { + Blt_Ts_SetAngle(axisPtr->limitsTextStyle, 90.0); + Blt_Ts_SetAnchor(axisPtr->limitsTextStyle, TK_ANCHOR_SE); + Blt_Ps_DrawText(ps, string, &axisPtr->limitsTextStyle, + (double)graphPtr->right, hMax); + hMax -= (textWidth + SPACING); + } else { + Blt_Ts_SetAngle(axisPtr->limitsTextStyle, 0.0); + Blt_Ts_SetAnchor(axisPtr->limitsTextStyle, TK_ANCHOR_NW); + Blt_Ps_DrawText(ps, string, &axisPtr->limitsTextStyle, + vMax, (double)graphPtr->top); + vMax += (textWidth + SPACING); + } + } + } + if (*minFmt != '\0') { + sprintf_s(string, 200, minFmt, axisPtr->axisRange.min); + Blt_GetTextExtents(axisPtr->tickFont, 0, string, -1, &textWidth, + &textHeight); + if ((textWidth > 0) && (textHeight > 0)) { + Blt_Ts_SetAnchor(axisPtr->limitsTextStyle, TK_ANCHOR_SW); + if (axisPtr->obj.classId == CID_AXIS_X) { + Blt_Ts_SetAngle(axisPtr->limitsTextStyle, 90.0); + Blt_Ps_DrawText(ps, string, &axisPtr->limitsTextStyle, + (double)graphPtr->left, hMin); + hMin -= (textWidth + SPACING); + } else { + Blt_Ts_SetAngle(axisPtr->limitsTextStyle, 0.0); + Blt_Ps_DrawText(ps, string, &axisPtr->limitsTextStyle, + vMin, (double)graphPtr->bottom); + vMin += (textWidth + SPACING); + } + } + } + } +} + +Axis * +Blt_GetFirstAxis(Blt_Chain chain) +{ + Blt_ChainLink link; + + link = Blt_Chain_FirstLink(chain); + if (link == NULL) { + return NULL; + } + return Blt_Chain_GetValue(link); +} + +Axis * +Blt_NearestAxis(Graph *graphPtr, int x, int y) +{ + Blt_HashEntry *hPtr; + Blt_HashSearch cursor; + + for (hPtr = Blt_FirstHashEntry(&graphPtr->axes.table, &cursor); + hPtr != NULL; hPtr = Blt_NextHashEntry(&cursor)) { + Axis *axisPtr; + + axisPtr = Blt_GetHashValue(hPtr); + if ((axisPtr->flags & (DELETE_PENDING|HIDE|AXIS_USE)) != AXIS_USE) { + continue; + } + if (axisPtr->flags & AXIS_SHOWTICKS) { + Blt_ChainLink link; + + for (link = Blt_Chain_FirstLink(axisPtr->tickLabels); link != NULL; + link = Blt_Chain_NextLink(link)) { + TickLabel *labelPtr; + Point2d t; + double rw, rh; + Point2d bbox[5]; + + labelPtr = Blt_Chain_GetValue(link); + Blt_GetBoundingBox(labelPtr->width, labelPtr->height, + axisPtr->tickAngle, &rw, &rh, bbox); + t = Blt_AnchorPoint(labelPtr->anchorPos.x, + labelPtr->anchorPos.y, rw, rh, axisPtr->tickAnchor); + t.x = x - t.x - (rw * 0.5); + t.y = y - t.y - (rh * 0.5); + + bbox[4] = bbox[0]; + if (Blt_PointInPolygon(&t, bbox, 5)) { + axisPtr->detail = "label"; + return axisPtr; + } + } + } + if (axisPtr->title != NULL) { /* and then the title string. */ + Point2d bbox[5]; + Point2d t; + double rw, rh; + unsigned int w, h; + + Blt_GetTextExtents(axisPtr->titleFont, 0, axisPtr->title,-1,&w,&h); + Blt_GetBoundingBox(w, h, axisPtr->titleAngle, &rw, &rh, bbox); + t = Blt_AnchorPoint(axisPtr->titlePos.x, axisPtr->titlePos.y, + rw, rh, axisPtr->titleAnchor); + /* Translate the point so that the 0,0 is the upper left + * corner of the bounding box. */ + t.x = x - t.x - (rw * 0.5); + t.y = y - t.y - (rh * 0.5); + + bbox[4] = bbox[0]; + if (Blt_PointInPolygon(&t, bbox, 5)) { + axisPtr->detail = "title"; + return axisPtr; + } + } + if (axisPtr->lineWidth > 0) { /* Check for the axis region */ + if ((x <= axisPtr->right) && (x >= axisPtr->left) && + (y <= axisPtr->bottom) && (y >= axisPtr->top)) { + axisPtr->detail = "line"; + return axisPtr; + } + } + } + return NULL; +} + +ClientData +Blt_MakeAxisTag(Graph *graphPtr, const char *tagName) +{ + Blt_HashEntry *hPtr; + int isNew; + + hPtr = Blt_CreateHashEntry(&graphPtr->axes.tagTable, tagName, &isNew); + return Blt_GetHashKey(&graphPtr->axes.tagTable, hPtr); +} + + + +/* + *--------------------------------------------------------------------------- + * + * TimeScaleAxis -- + * + * Determine the units of a linear scaled axis. + * + * The axis limits are either the range of the data values mapped + * to the axis (autoscaled), or the values specified by the -min + * and -max options (manual). + * + * If autoscaled, the smallest and largest major ticks will + * encompass the range of data values. If the -loose option is + * selected, the next outer ticks are choosen. If tight, the + * ticks are at or inside of the data limits are used. + * + * If manually set, the ticks are at or inside the data limits + * are used. This makes sense for zooming. You want the + * selected range to represent the next limit, not something a + * bit bigger. + * + * Note: I added an "always" value to the -loose option to force + * the manually selected axes to be loose. It's probably + * not a good idea. + * + * maxY + * | units = magnitude (of least significant digit) + * | high = largest unit tick < max axis value + * high _| low = smallest unit tick > min axis value + * | + * | range = high - low + * | # ticks = greatest factor of range/units + * _| + * U | + * n | + * i | + * t _| + * | + * | + * | + * low _| + * | + * |_minX________________maxX__ + * | | | | | + * minY low high + * minY + * + * numTicks = Number of ticks + * min = Minimum value of axis + * max = Maximum value of axis + * range = Range of values (max - min) + * + * Results: + * None. + * + * Side Effects: + * The axis tick information is set. The actual tick values will + * be generated later. + * + *--------------------------------------------------------------------------- + */ +static void +TimeScaleAxis(Axis *axisPtr, double min, double max) +{ +#ifdef notdef + double step; + double tickMin, tickMax; + double axisMin, axisMax; + unsigned int nTicks; + + range = max - min; + + +#define SECONDS_MIN (60) +#define SECONDS_HOUR (SECONDS_MIN * 60) +#define SECONDS_DAY (SECONDS_HOUR * 24) +#define SECONDS_MONTH (SECONDS_DAY * 30) +#define SECONDS_YEAR (SECONDS_MONTH * 12) + div = nTicks - 1; + if (range > (SECONDS_MONTH * 6 * div)) { + unit = TICK_YEAR; + } else if (range > (SECONDS_MONTH * 3 * div)) { + unit = TICKS_6MONTH; + first = timefloor(min, unit); + last = timeceil(max, unit); + } else if (range > (SECONDS_MONTH * 2 * div)) { + unit = TICKS_3MONTH; + first = timefloor(min, unit); + last = timeceil(max, unit); + } else if (range > (SECONDS_MONTH * div)) { + unit = TICKS_2MONTH; + first = timefloor(min, unit); + last = timeceil(max, unit); + } else if (range > (SECONDS_DAY * 15 * div)) { + unit = TICKS_1MONTH; + } else if (range > (SECONDS_DAY * 10 * div)) { + unit = TICKS_15DAY; + } else if (range > (SECONDS_DAY * div)) { + unit = TICKS_10DAY; + } else if (range > (SECONDS_HOUR * 12 * div)) { + unit = TICKS_1DAY; + } else if (range > (SECONDS_HOUR * 6 * div)) { + unit = TICKS_12HOUR; + } else if (range > (SECONDS_HOUR * 3 * div)) { + unit = TICKS_6HOUR; + } else if (range > (SECONDS_HOUR * div)) { + unit = TICKS_3HOUR; + } else if (range > (SECONDS_MIN * 30 * div)) { + unit = TICKS_HOUR; + } else if (range > (SECONDS_MIN * 20 * div)) { + unit = TICKS_30MIN; + } else if (range > (SECONDS_MIN * 15 * div)) { + unit = TICKS_20MIN; + } else if (range > (SECONDS_MIN * 10 * div)) { + unit = TICKS_15MIN; + } else if (range > (SECONDS_MIN * div)) { + unit = TICKS_10MIN; + } else if (range > (SECONDS_SEC * 30 * div)) { + unit = TICKS_1MIN; + } else if (range > (SECONDS_SEC * 20 * div)) { + unit = TICKS_30SEC; + } else if (range > (SECONDS_SEC * 15 * div)) { + unit = TICKS_20SEC; + } else if (range > (SECONDS_SEC * 10 * div)) { + unit = TICKS_15SEC; + } else if (range > (SECONDS_SEC * div)) { + unit = TICKS_10SEC; + } else { + unit = TICKS_1SEC; + } + + } else { + unit = TICKS_SECS; + } + nTicks = 0; + step = 1.0; + /* Suppress compiler warning. */ + axisMin = axisMax = tickMin = tickMax = Blt_NaN(); + if (min < max) { + double range; + + range = max - min; + /* Calculate the major tick stepping. */ + if (axisPtr->reqStep > 0.0) { + /* An interval was designated by the user. Keep scaling it until + * it fits comfortably within the current range of the axis. */ + step = axisPtr->reqStep; + while ((2 * step) >= range) { + step *= 0.5; + } + } else { + range = NiceNum(range, 0); + step = NiceNum(range / axisPtr->reqNumMajorTicks, 1); + } + + /* Find the outer tick values. Add 0.0 to prevent getting -0.0. */ + axisMin = tickMin = floor(min / step) * step + 0.0; + axisMax = tickMax = ceil(max / step) * step + 0.0; + + nTicks = Round((tickMax - tickMin) / step) + 1; + } + axisPtr->majorSweep.step = step; + axisPtr->majorSweep.initial = tickMin; + axisPtr->majorSweep.nSteps = nTicks; + + /* + * The limits of the axis are either the range of the data ("tight") or at + * the next outer tick interval ("loose"). The looseness or tightness has + * to do with how the axis fits the range of data values. This option is + * overridden when the user sets an axis limit (by either -min or -max + * option). The axis limit is always at the selected limit (otherwise we + * assume that user would have picked a different number). + */ + if ((axisPtr->looseMin == AXIS_TIGHT) || + ((axisPtr->looseMin == AXIS_LOOSE) && + (DEFINED(axisPtr->reqMin)))) { + axisMin = min; + } + if ((axisPtr->looseMax == AXIS_TIGHT) || + ((axisPtr->looseMax == AXIS_LOOSE) && + (DEFINED(axisPtr->reqMax)))) { + axisMax = max; + } + SetAxisRange(&axisPtr->axisRange, axisMin, axisMax); + + /* Now calculate the minor tick step and number. */ + + if ((axisPtr->reqNumMinorTicks > 0) && (axisPtr->flags & AXIS_AUTO_MAJOR)) { + nTicks = axisPtr->reqNumMinorTicks - 1; + step = 1.0 / (nTicks + 1); + } else { + nTicks = 0; /* No minor ticks. */ + step = 0.5; /* Don't set the minor tick interval to + * 0.0. It makes the GenerateTicks + * routine * create minor log-scale tick + * marks. */ + } + axisPtr->minorSweep.initial = axisPtr->minorSweep.step = step; + axisPtr->minorSweep.nSteps = nTicks; +#endif +} + +#ifdef notdef +static Ticks * +TimeGenerateTicks(TickSweep *sweepPtr) +{ + Ticks *ticksPtr; + + ticksPtr = Blt_AssertMalloc(sizeof(Ticks) + + (sweepPtr->nSteps * sizeof(double))); + ticksPtr->nTicks = 0; + + if (sweepPtr->step == 0.0) { + /* Hack: A zero step indicates to use log values. */ + int i; + /* Precomputed log10 values [1..10] */ + static double logTable[] = { + 0.0, + 0.301029995663981, + 0.477121254719662, + 0.602059991327962, + 0.698970004336019, + 0.778151250383644, + 0.845098040014257, + 0.903089986991944, + 0.954242509439325, + 1.0 + }; + for (i = 0; i < sweepPtr->nSteps; i++) { + ticksPtr->values[i] = logTable[i]; + } + } else { + double value; + int i; + + value = sweepPtr->initial; /* Start from smallest axis tick */ + for (i = 0; i < sweepPtr->nSteps; i++) { + value = UROUND(value, sweepPtr->step); + ticksPtr->values[i] = value; + value += sweepPtr->step; + } + } + ticksPtr->nTicks = sweepPtr->nSteps; + return ticksPtr; +} + +static double +TimeFloor(double min, int unit) +{ + unsigned long ticks; + + ticks = (long)floor(min); + localtime_r(&ticks, &tm); + switch(unit) { + case TICK_6MONTHS: + tm.sec = 0; + tm.min = 0; + tm.day = 0; + tm. + } +} +static double +TimeCeil(double max, int unit) +{ + +} + +#endif + diff --git a/src/bltGrAxis.h b/src/bltGrAxis.h new file mode 100644 index 0000000..a009aec --- /dev/null +++ b/src/bltGrAxis.h @@ -0,0 +1,332 @@ + +/* + * bltGrAxis.h -- + * + * Copyright 1993-2004 George A Howlett. + * + * 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 AUTHORS + * OR COPYRIGHT HOLDERS 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. + */ + +#ifndef _BLT_GR_AXIS_H +#define _BLT_GR_AXIS_H + +#include "bltList.h" + +/* + *--------------------------------------------------------------------------- + * + * Grid -- + * + * Contains attributes of describing how to draw grids (at major ticks) + * in the graph. Grids may be mapped to either/both X and Y axis. + * + *--------------------------------------------------------------------------- + */ +typedef struct { + Blt_Dashes dashes; /* Dash style of the grid. This represents an + * array of alternatingly drawn pixel + * values. */ + int lineWidth; /* Width of the grid lines */ + XColor *color; /* Color of the grid lines */ + GC gc; /* Graphics context for the grid. */ + + Segment2d *segments; /* Array of line segments representing the + * grid lines */ + int nUsed; /* # of axis segments in use. */ + int nAllocated; /* # of axis segments allocated. */ +} Grid; + +/* + *--------------------------------------------------------------------------- + * + * AxisRange -- + * + * Designates a range of values by a minimum and maximum limit. + * + *--------------------------------------------------------------------------- + */ +typedef struct { + double min, max, range, scale; +} AxisRange; + +/* + *--------------------------------------------------------------------------- + * + * TickLabel -- + * + * Structure containing the X-Y screen coordinates of the tick + * label (anchored at its center). + * + *--------------------------------------------------------------------------- + */ +typedef struct { + Point2d anchorPos; + unsigned int width, height; + char string[1]; +} TickLabel; + +/* + *--------------------------------------------------------------------------- + * + * Ticks -- + * + * Structure containing information where the ticks (major or + * minor) will be displayed on the graph. + * + *--------------------------------------------------------------------------- + */ +typedef struct { + unsigned int nTicks; /* # of ticks on axis */ + double values[1]; /* Array of tick values (malloc-ed). */ +} Ticks; + +/* + *--------------------------------------------------------------------------- + * + * TickSweep -- + * + * Structure containing information where the ticks (major or + * minor) will be displayed on the graph. + * + *--------------------------------------------------------------------------- + */ +typedef struct { + double initial; /* Initial value */ + double step; /* Size of interval */ + unsigned int nSteps; /* Number of intervals. */ +} TickSweep; + +/* + *--------------------------------------------------------------------------- + * + * Axis -- + * + * Structure contains options controlling how the axis will be + * displayed. + * + *--------------------------------------------------------------------------- + */ +typedef struct { + GraphObj obj; /* Must be first field in axis. */ + + unsigned int flags; + + Blt_HashEntry *hashPtr; + + /* Fields specific to axes. */ + + const char *detail; + + int refCount; /* Number of elements referencing this + * axis. */ + int logScale; /* If non-zero, generate log scale + * ticks for the axis. */ + int timeScale; /* If non-zero, generate time scale + * ticks for the axis. This option is + * overridden by -logscale. */ + int descending; /* If non-zero, display the range of + * values on the axis in descending + * order, from high to low. */ + + int looseMin, looseMax; /* If non-zero, axis range extends to + * the outer major ticks, otherwise at + * the limits of the data values. This + * is overriddened by setting the -min + * and -max options. */ + + const char *title; /* Title of the axis. */ + + int titleAlternate; /* Indicates whether to position the + * title above/left of the axis. */ + + Point2d titlePos; /* Position of the title */ + + unsigned short int titleWidth, titleHeight; + + + int lineWidth; /* Width of lines representing axis + * (including ticks). If zero, then + * no axis lines or ticks are + * drawn. */ + + const char **limitsFormats; /* One or two strings of sprintf-like + * formats describing how to display + * virtual axis limits. If NULL, + * display no limits. */ + int nFormats; + + TextStyle limitsTextStyle; /* Text attributes (color, font, + * rotation, etc.) of the limits. */ + + double windowSize; /* Size of a sliding window of values + * used to scale the axis + * automatically as new data values + * are added. The axis will always + * display the latest values in this + * range. */ + + double shiftBy; /* Shift maximum by this interval. */ + + int tickLength; /* Length of major ticks in pixels */ + + const char *formatCmd; /* Specifies a TCL command, to be + * invoked by the axis whenever it has + * to generate tick labels. */ + + Tcl_Obj *scrollCmdObjPtr; + int scrollUnits; + + double min, max; /* The actual axis range. */ + + double reqMin, reqMax; /* Requested axis bounds. Consult the + * axisPtr->flags field for + * AXIS_CONFIG_MIN and AXIS_CONFIG_MAX + * to see if the requested bound have + * been set. They override the + * computed range of the axis + * (determined by auto-scaling). */ + + double reqScrollMin, reqScrollMax; + + double scrollMin, scrollMax; /* Defines the scrolling reqion of the + * axis. Normally the region is + * determined from the data limits. If + * specified, these values override + * the data-range. */ + + AxisRange valueRange; /* Range of data values of elements + * mapped to this axis. This is used + * to auto-scale the axis in "tight" + * mode. */ + AxisRange axisRange; /* Smallest and largest major tick + * values for the axis. The tick + * values lie outside the range of + * data values. This is used to + * auto-scale the axis in "loose" + * mode. */ + + double prevMin, prevMax; + + double reqStep; /* If > 0.0, overrides the computed major + * tick interval. Otherwise a stepsize + * is automatically calculated, based + * upon the range of elements mapped to the + * axis. The default value is 0.0. */ + + Ticks *t1Ptr; /* Array of major tick positions. May be + * set by the user or generated from the + * major sweep below. */ + + Ticks *t2Ptr; /* Array of minor tick positions. May be + * set by the user or generated from the + * minor sweep below. */ + + TickSweep minorSweep, majorSweep; + + int reqNumMajorTicks; /* Default number of ticks to be displayed. */ + int reqNumMinorTicks; /* If non-zero, represents the + * requested the number of minor ticks + * to be uniformally displayed along + * each major tick. */ + + + int labelOffset; /* If non-zero, indicates that the tick + * label should be offset to sit in the + * middle of the next interval. */ + + /* The following fields are specific to logical axes */ + + int margin; /* Margin that contains this axis. */ + Blt_ChainLink link; /* Axis link in margin list. */ + Blt_Chain chain; + Segment2d *segments; /* Array of line segments representing + * the major and minor ticks, but also + * the * axis line itself. The segment + * coordinates * are relative to the + * axis. */ + int nSegments; /* Number of segments in the above + * array. */ + Blt_Chain tickLabels; /* Contains major tick label strings + * and their offsets along the + * axis. */ + short int left, right, top, bottom; /* Region occupied by the of axis. */ + short int width, height; /* Extents of axis */ + short int maxTickWidth, maxTickHeight; + Blt_Background normalBg; + Blt_Background activeBg; + XColor *activeFgColor; + + int relief; + int borderWidth; + int activeRelief; + + float tickAngle; + Blt_Font tickFont; + Tk_Anchor tickAnchor; + Tk_Anchor reqTickAnchor; + XColor *tickColor; + GC tickGC; /* Graphics context for axis and tick + * labels */ + GC activeTickGC; + + double titleAngle; + Blt_Font titleFont; + Tk_Anchor titleAnchor; + Tk_Justify titleJustify; + XColor *titleColor; + + Grid major, minor; /* Axis grid information. */ + + double screenScale; + int screenMin, screenRange; + +} Axis; + +/* + *--------------------------------------------------------------------------- + * + * Axis2d -- + * + * The pair of axes mapping a point onto the graph. + * + *--------------------------------------------------------------------------- + */ +typedef struct { + Axis *x, *y; +} Axis2d; + +/* Axis flags: */ + +#define AXIS_AUTO_MAJOR (1<<16) /* Auto-generate major ticks. */ +#define AXIS_AUTO_MINOR (1<<17) /* Auto-generate minor ticks. */ +#define AXIS_ONSCREEN (1<<18) /* Axis is displayed on the screen via + * the "use" operation */ +#define AXIS_GRID (1<<19) +#define AXIS_GRID_MINOR (1<<20) +#define AXIS_TICKS (1<<21) +#define AXIS_TICKS_INTERIOR (1<<22) +#define AXIS_CHECK_LIMITS (1<<23) +#define AXIS_LOGSCALE (1<<24) +#define AXIS_DECREASING (1<<25) + +#endif /* _BLT_GR_AXIS_H */ diff --git a/src/bltGrBind.C b/src/bltGrBind.C new file mode 100644 index 0000000..7b288f7 --- /dev/null +++ b/src/bltGrBind.C @@ -0,0 +1,1825 @@ + +/* + * bltBind.c -- + * + * This module implements object binding procedures for the BLT toolkit. + * + * Copyright 1998 George A Howlett. + * + * 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 AUTHORS OR COPYRIGHT HOLDERS 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. + */ + +#include "bltInt.h" +#include "bltBind.h" +#include + +static Tk_EventProc BindProc; + +typedef struct _Blt_BindTable BindTable; + +/* Make button presses on objects have implicit grab. */ +#define FULLY_SIMULATE_GRAB 1 + +/* + * Binding table procedures. + */ +#define REPICK_IN_PROGRESS (1<<0) +#define LEFT_GRABBED_ITEM (1<<1) + +#define ALL_BUTTONS_MASK \ + (Button1Mask | Button2Mask | Button3Mask | Button4Mask | Button5Mask) + +#ifndef VirtualEventMask +#define VirtualEventMask (1L << 30) +#endif + +#define ALL_VALID_EVENTS_MASK \ + (ButtonMotionMask | Button1MotionMask | Button2MotionMask | \ + Button3MotionMask | Button4MotionMask | Button5MotionMask | \ + ButtonPressMask | ButtonReleaseMask | EnterWindowMask | \ + LeaveWindowMask | KeyPressMask | KeyReleaseMask | \ + PointerMotionMask | VirtualEventMask) + +static int buttonMasks[] = +{ + 0, /* No buttons pressed */ + Button1Mask, Button2Mask, Button3Mask, Button4Mask, Button5Mask, +}; + + +/* + * How to make drag&drop work? + * + * Right now we generate pseudo events within button grab + * on an object. They're marked NotifyVirtual instead of NotifyAncestor. + * A better solution: generate new-style virtual <> + * <> <> events. These virtual events don't have + * to exist as "real" event sequences, like virtual events do now. + */ + +/* + *--------------------------------------------------------------------------- + * + * DoEvent -- + * + * This procedure is called to invoke binding processing for a new event + * that is associated with the current item for a legend. + * + * Results: + * None. + * + * Side effects: + * Depends on the bindings for the legend. A binding script could delete + * an entry, so callers should protect themselves with Tcl_Preserve and + * Tcl_Release. + * + *--------------------------------------------------------------------------- + */ +static void +DoEvent( + BindTable *bindPtr, /* Binding information for widget in which + * event occurred. */ + XEvent *eventPtr, /* Real or simulated X event that is to be + * processed. */ + ClientData item, /* Item picked. */ + ClientData context) /* Context of item. */ +{ + Blt_List tagList; + + if ((bindPtr->tkwin == NULL) || (bindPtr->bindingTable == NULL)) { + return; + } + if ((eventPtr->type == KeyPress) || (eventPtr->type == KeyRelease)) { + item = bindPtr->focusItem; + context = bindPtr->focusContext; + } + if (item == NULL) { + return; + } + /* + * Invoke the binding system. + */ + tagList = Blt_List_Create(BLT_ONE_WORD_KEYS); + if (bindPtr->tagProc == NULL) { + Blt_List_Append(tagList, Tk_GetUid("all"), 0); + Blt_List_Append(tagList, (char *)item, 0); + } else { + (*bindPtr->tagProc) (bindPtr, item, context, tagList); + } + if (Blt_List_GetLength(tagList) > 0) { + int nTags; + ClientData *tagArray; +#define MAX_STATIC_TAGS 64 + ClientData staticTags[MAX_STATIC_TAGS]; + Blt_ListNode node; + + tagArray = staticTags; + nTags = Blt_List_GetLength(tagList); + if (nTags >= MAX_STATIC_TAGS) { + tagArray = Blt_AssertMalloc(sizeof(ClientData) * nTags); + + } + nTags = 0; + for (node = Blt_List_FirstNode(tagList); node != NULL; + node = Blt_List_NextNode(node)) { + tagArray[nTags++] = (ClientData)Blt_List_GetKey(node); + } + Tk_BindEvent(bindPtr->bindingTable, eventPtr, bindPtr->tkwin, nTags, + tagArray); + if (tagArray != staticTags) { + Blt_Free(tagArray); + } + } + Blt_List_Destroy(tagList); +} + +/* + *--------------------------------------------------------------------------- + * + * PickCurrentItem -- + * + * Find the topmost item in a legend that contains a given location and + * mark the the current item. If the current item has changed, generate + * a fake exit event on the old current item and a fake enter event on + * the new current item. + * + * Results: + * None. + * + * Side effects: + * The current item may change. If it does, then the commands associated + * with item entry and exit could do just about anything. A binding + * script could delete the legend, so callers should protect themselves + * with Tcl_Preserve and Tcl_Release. + * + *--------------------------------------------------------------------------- + */ +static void +PickCurrentItem( + BindTable *bindPtr, /* Binding table information. */ + XEvent *eventPtr) /* Event describing location of mouse cursor. + * Must be EnterWindow, LeaveWindow, + * ButtonRelease, or MotionNotify. */ +{ + int buttonDown; + ClientData newItem, oldItem; + ClientData newContext; + + /* + * Check whether or not a button is down. If so, we'll log entry and exit + * into and out of the current item, but not entry into any other item. + * This implements a form of grabbing equivalent to what the X server does + * for windows. + */ + buttonDown = (bindPtr->state & ALL_BUTTONS_MASK); + if (!buttonDown) { + bindPtr->flags &= ~LEFT_GRABBED_ITEM; + } + + /* + * Save information about this event in the widget. The event in the + * widget is used for two purposes: + * + * 1. Event bindings: if the current item changes, fake events are + * generated to allow item-enter and item-leave bindings to trigger. + * 2. Reselection: if the current item gets deleted, can use the + * saved event to find a new current item. + * Translate MotionNotify events into EnterNotify events, since that's + * what gets reported to item handlers. + */ + + if (eventPtr != &bindPtr->pickEvent) { + if ((eventPtr->type == MotionNotify) || + (eventPtr->type == ButtonRelease)) { + bindPtr->pickEvent.xcrossing.type = EnterNotify; + bindPtr->pickEvent.xcrossing.serial = eventPtr->xmotion.serial; + bindPtr->pickEvent.xcrossing.send_event = + eventPtr->xmotion.send_event; + bindPtr->pickEvent.xcrossing.display = eventPtr->xmotion.display; + bindPtr->pickEvent.xcrossing.window = eventPtr->xmotion.window; + bindPtr->pickEvent.xcrossing.root = eventPtr->xmotion.root; + bindPtr->pickEvent.xcrossing.subwindow = None; + bindPtr->pickEvent.xcrossing.time = eventPtr->xmotion.time; + bindPtr->pickEvent.xcrossing.x = eventPtr->xmotion.x; + bindPtr->pickEvent.xcrossing.y = eventPtr->xmotion.y; + bindPtr->pickEvent.xcrossing.x_root = eventPtr->xmotion.x_root; + bindPtr->pickEvent.xcrossing.y_root = eventPtr->xmotion.y_root; + bindPtr->pickEvent.xcrossing.mode = NotifyNormal; + bindPtr->pickEvent.xcrossing.detail = NotifyNonlinear; + bindPtr->pickEvent.xcrossing.same_screen + = eventPtr->xmotion.same_screen; + bindPtr->pickEvent.xcrossing.focus = False; + bindPtr->pickEvent.xcrossing.state = eventPtr->xmotion.state; + } else { + bindPtr->pickEvent = *eventPtr; + } + } + bindPtr->activePick = TRUE; + + /* + * If this is a recursive call (there's already a partially completed call + * pending on the stack; it's in the middle of processing a Leave event + * handler for the old current item) then just return; the pending call + * will do everything that's needed. + */ + if (bindPtr->flags & REPICK_IN_PROGRESS) { + return; + } + /* + * A LeaveNotify event automatically means that there's no current item, + * so the check for closest item can be skipped. + */ + newContext = NULL; + if (bindPtr->pickEvent.type != LeaveNotify) { + int x, y; + + x = bindPtr->pickEvent.xcrossing.x; + y = bindPtr->pickEvent.xcrossing.y; + newItem = (*bindPtr->pickProc) (bindPtr->clientData, x, y, &newContext); + } else { + newItem = NULL; + } + if (((newItem == bindPtr->currentItem) && + (newContext == bindPtr->currentContext)) && + ((bindPtr->flags & LEFT_GRABBED_ITEM) == 0)) { + /* + * Nothing to do: the current item hasn't changed. + */ + return; + } +#if FULLY_SIMULATE_GRAB + if (((newItem != bindPtr->currentItem) || + (newContext != bindPtr->currentContext)) && + (buttonDown)) { + bindPtr->flags |= LEFT_GRABBED_ITEM; +#ifdef notdef + fprintf(stderr, "pickcurrentitem: simulate grab.\n"); +#endif + return; + } +#endif + /* + * Simulate a LeaveNotify event on the previous current item and an + * EnterNotify event on the new current item. Remove the "current" tag + * from the previous current item and place it on the new current item. + */ + oldItem = bindPtr->currentItem; + Tcl_Preserve(oldItem); + Tcl_Preserve(newItem); + + if ((bindPtr->currentItem != NULL) && + ((newItem != bindPtr->currentItem) || + (newContext != bindPtr->currentContext)) && + ((bindPtr->flags & LEFT_GRABBED_ITEM) == 0)) { + XEvent event; + + event = bindPtr->pickEvent; + event.type = LeaveNotify; + /* + * If the event's detail happens to be NotifyInferior the binding + * mechanism will discard the event. To be consistent, always use + * NotifyAncestor. + */ + event.xcrossing.detail = NotifyAncestor; + + bindPtr->flags |= REPICK_IN_PROGRESS; + DoEvent(bindPtr, &event, bindPtr->currentItem, bindPtr->currentContext); + bindPtr->flags &= ~REPICK_IN_PROGRESS; + + /* + * Note: during DoEvent above, it's possible that bindPtr->newItem got + * reset to NULL because the item was deleted. + */ + } + if (((newItem != bindPtr->currentItem) || + (newContext != bindPtr->currentContext)) && + (buttonDown)) { + XEvent event; + + bindPtr->flags |= LEFT_GRABBED_ITEM; + event = bindPtr->pickEvent; + if ((newItem != bindPtr->newItem) || + (newContext != bindPtr->newContext)) { + ClientData savedItem; + ClientData savedContext; + + /* + * Generate and events for objects during button + * grabs. This isn't standard. But for example, it allows one to + * provide balloon help on the individual entries of the Hierbox + * widget. + */ + savedItem = bindPtr->currentItem; + savedContext = bindPtr->currentContext; + if (bindPtr->newItem != NULL) { + event.type = LeaveNotify; + event.xcrossing.detail = NotifyVirtual /* Ancestor */ ; + bindPtr->currentItem = bindPtr->newItem; + DoEvent(bindPtr, &event, bindPtr->newItem, bindPtr->newContext); + } + bindPtr->newItem = newItem; + bindPtr->newContext = newContext; + if (newItem != NULL) { + event.type = EnterNotify; + event.xcrossing.detail = NotifyVirtual /* Ancestor */ ; + bindPtr->currentItem = newItem; + DoEvent(bindPtr, &event, newItem, newContext); + } + bindPtr->currentItem = savedItem; + bindPtr->currentContext = savedContext; + } + goto done; + } + /* + * Special note: it's possible that + * bindPtr->newItem == bindPtr->currentItem + * here. This can happen, for example, if LEFT_GRABBED_ITEM was set. + */ + + bindPtr->flags &= ~LEFT_GRABBED_ITEM; + bindPtr->currentItem = bindPtr->newItem = newItem; + bindPtr->currentContext = bindPtr->newContext = newContext; + if (bindPtr->currentItem != NULL) { + XEvent event; + + event = bindPtr->pickEvent; + event.type = EnterNotify; + event.xcrossing.detail = NotifyAncestor; + DoEvent(bindPtr, &event, newItem, newContext); +#ifdef notdef + if ((eventPtr->type == MotionNotify) || + (eventPtr->type == ButtonRelease)) { + fprintf(stderr, "pickcurrentitem: DoEvent Button buttondown=%d.\n", + buttonDown); + event.type = eventPtr->type; + event.xbutton.button = eventPtr->xbutton.button; + DoEvent(bindPtr, &event, newItem, newContext); + fprintf(stderr, "pickcurrentitem: done.\n"); + } +#endif + } + done: + Tcl_Release(newItem); + Tcl_Release(oldItem); +} + +/* + *--------------------------------------------------------------------------- + * + * BindProc -- + * + * This procedure is invoked by the Tk dispatcher to handle events + * associated with bindings on items. + * + * Results: + * None. + * + * Side effects: + * Depends on the command invoked as part of the binding + * (if there was any). + * + *--------------------------------------------------------------------------- + */ +static void +BindProc( + ClientData clientData, /* Pointer to widget structure. */ + XEvent *eventPtr) /* Pointer to X event that just happened. */ +{ + BindTable *bindPtr = clientData; + int mask; + + Tcl_Preserve(bindPtr->clientData); + /* + * This code below keeps track of the current modifier state in + * bindPtr->state. This information is used to defer repicks of the + * current item while buttons are down. + */ + switch (eventPtr->type) { + case ButtonPress: + case ButtonRelease: + mask = 0; + if ((eventPtr->xbutton.button >= Button1) && + (eventPtr->xbutton.button <= Button5)) { + mask = buttonMasks[eventPtr->xbutton.button]; + } + /* + * For button press events, repick the current item using the button + * state before the event, then process the event. For button release + * events, first process the event, then repick the current item using + * the button state *after* the event (the button has logically gone + * up before we change the current item). + */ + if (eventPtr->type == ButtonPress) { + + /* + * On a button press, first repick the current item using the + * button state before the event, the process the event. + */ + + bindPtr->state = eventPtr->xbutton.state; + PickCurrentItem(bindPtr, eventPtr); + bindPtr->state ^= mask; + DoEvent(bindPtr, eventPtr, bindPtr->currentItem, + bindPtr->currentContext); + + } else { + + /* + * Button release: first process the event, with the button still + * considered to be down. Then repick the current item under the + * assumption that the button is no longer down. + */ + bindPtr->state = eventPtr->xbutton.state; + DoEvent(bindPtr, eventPtr, bindPtr->currentItem, + bindPtr->currentContext); + eventPtr->xbutton.state ^= mask; + bindPtr->state = eventPtr->xbutton.state; + PickCurrentItem(bindPtr, eventPtr); + eventPtr->xbutton.state ^= mask; + } + break; + + case EnterNotify: + case LeaveNotify: + bindPtr->state = eventPtr->xcrossing.state; + PickCurrentItem(bindPtr, eventPtr); + break; + + case MotionNotify: + bindPtr->state = eventPtr->xmotion.state; + PickCurrentItem(bindPtr, eventPtr); + DoEvent(bindPtr, eventPtr, bindPtr->currentItem, + bindPtr->currentContext); + break; + + case KeyPress: + case KeyRelease: + bindPtr->state = eventPtr->xkey.state; + PickCurrentItem(bindPtr, eventPtr); + DoEvent(bindPtr, eventPtr, bindPtr->currentItem, + bindPtr->currentContext); + break; + } + Tcl_Release(bindPtr->clientData); +} + +int +Blt_ConfigureBindings( + Tcl_Interp *interp, + BindTable *bindPtr, + ClientData item, + int argc, + const char **argv) +{ + const char *command; + unsigned long mask; + const char *seq; + + if (argc == 0) { + Tk_GetAllBindings(interp, bindPtr->bindingTable, item); + return TCL_OK; + } + if (argc == 1) { + command = Tk_GetBinding(interp, bindPtr->bindingTable, item, argv[0]); + if (command == NULL) { + Tcl_AppendResult(interp, "can't find event \"", argv[0], "\"", + (char *)NULL); + return TCL_ERROR; + } + Tcl_SetStringObj(Tcl_GetObjResult(interp), command, -1); + return TCL_OK; + } + + seq = argv[0]; + command = argv[1]; + + if (command[0] == '\0') { + return Tk_DeleteBinding(interp, bindPtr->bindingTable, item, seq); + } + + if (command[0] == '+') { + mask = Tk_CreateBinding(interp, bindPtr->bindingTable, item, seq, + command + 1, TRUE); + } else { + mask = Tk_CreateBinding(interp, bindPtr->bindingTable, item, seq, + command, FALSE); + } + if (mask == 0) { + Tcl_AppendResult(interp, "event mask can't be zero for \"", item, "\"", + (char *)NULL); + return TCL_ERROR; + } + if (mask & (unsigned)~ALL_VALID_EVENTS_MASK) { + Tk_DeleteBinding(interp, bindPtr->bindingTable, item, seq); + Tcl_ResetResult(interp); + Tcl_AppendResult(interp, "requested illegal events; ", + "only key, button, motion, enter, leave, and virtual ", + "events may be used", (char *)NULL); + return TCL_ERROR; + } + return TCL_OK; +} + + +int +Blt_ConfigureBindingsFromObj( + Tcl_Interp *interp, + BindTable *bindPtr, + ClientData item, + int objc, + Tcl_Obj *const *objv) +{ + const char *command; + unsigned long mask; + const char *seq; + const char *string; + + if (objc == 0) { + Tk_GetAllBindings(interp, bindPtr->bindingTable, item); + return TCL_OK; + } + string = Tcl_GetString(objv[0]); + if (objc == 1) { + command = Tk_GetBinding(interp, bindPtr->bindingTable, item, string); + if (command == NULL) { + Tcl_ResetResult(interp); + Tcl_AppendResult(interp, "invalid binding event \"", string, "\"", + (char *)NULL); + return TCL_ERROR; + } + Tcl_SetStringObj(Tcl_GetObjResult(interp), command, -1); + return TCL_OK; + } + + seq = string; + command = Tcl_GetString(objv[1]); + + if (command[0] == '\0') { + return Tk_DeleteBinding(interp, bindPtr->bindingTable, item, seq); + } + + if (command[0] == '+') { + mask = Tk_CreateBinding(interp, bindPtr->bindingTable, item, seq, + command + 1, TRUE); + } else { + mask = Tk_CreateBinding(interp, bindPtr->bindingTable, item, seq, + command, FALSE); + } + if (mask == 0) { + return TCL_ERROR; + } + if (mask & (unsigned)~ALL_VALID_EVENTS_MASK) { + Tk_DeleteBinding(interp, bindPtr->bindingTable, item, seq); + Tcl_ResetResult(interp); + Tcl_AppendResult(interp, "requested illegal events; ", + "only key, button, motion, enter, leave, and virtual ", + "events may be used", (char *)NULL); + return TCL_ERROR; + } + return TCL_OK; +} + +Blt_BindTable +Blt_CreateBindingTable( + Tcl_Interp *interp, + Tk_Window tkwin, + ClientData clientData, + Blt_BindPickProc *pickProc, + Blt_BindTagProc *tagProc) +{ + unsigned int mask; + BindTable *bindPtr; + + bindPtr = Blt_AssertCalloc(1, sizeof(BindTable)); + bindPtr->bindingTable = Tk_CreateBindingTable(interp); + bindPtr->clientData = clientData; + bindPtr->tkwin = tkwin; + bindPtr->pickProc = pickProc; + bindPtr->tagProc = tagProc; + mask = (KeyPressMask | KeyReleaseMask | ButtonPressMask | + ButtonReleaseMask | EnterWindowMask | LeaveWindowMask | + PointerMotionMask); + Tk_CreateEventHandler(tkwin, mask, BindProc, bindPtr); + return bindPtr; +} + +void +Blt_DestroyBindingTable(BindTable *bindPtr) +{ + unsigned int mask; + + Tk_DeleteBindingTable(bindPtr->bindingTable); + mask = (KeyPressMask | KeyReleaseMask | ButtonPressMask | + ButtonReleaseMask | EnterWindowMask | LeaveWindowMask | + PointerMotionMask); + Tk_DeleteEventHandler(bindPtr->tkwin, mask, BindProc, bindPtr); + Blt_Free(bindPtr); +} + +void +Blt_PickCurrentItem(BindTable *bindPtr) +{ + if (bindPtr->activePick) { + PickCurrentItem(bindPtr, &bindPtr->pickEvent); + } +} + +void +Blt_DeleteBindings( + BindTable *bindPtr, + ClientData object) +{ + Tk_DeleteAllBindings(bindPtr->bindingTable, object); + + /* + * If this is the object currently picked, we need to repick one. + */ + if (bindPtr->currentItem == object) { + bindPtr->currentItem = NULL; + bindPtr->currentContext = NULL; + } + if (bindPtr->newItem == object) { + bindPtr->newItem = NULL; + bindPtr->newContext = NULL; + } + if (bindPtr->focusItem == object) { + bindPtr->focusItem = NULL; + bindPtr->focusContext = NULL; + } +} + +void +Blt_MoveBindingTable( + BindTable *bindPtr, + Tk_Window tkwin) +{ + unsigned int mask; + + mask = (KeyPressMask | KeyReleaseMask | ButtonPressMask | + ButtonReleaseMask | EnterWindowMask | LeaveWindowMask | + PointerMotionMask); + if (bindPtr->tkwin != NULL) { + Tk_DeleteEventHandler(bindPtr->tkwin, mask, BindProc, bindPtr); + } + Tk_CreateEventHandler(tkwin, mask, BindProc, bindPtr); + bindPtr->tkwin = tkwin; +} + +/* + * The following union is used to hold the detail information from an + * XEvent (including Tk's XVirtualEvent extension). + */ +typedef union { + KeySym keySym; /* KeySym that corresponds to xkey.keycode. */ + int button; /* Button that was pressed (xbutton.button). */ + Tk_Uid name; /* Tk_Uid of virtual event. */ + ClientData clientData; /* Used when type of Detail is unknown, and to + * ensure that all bytes of Detail are initialized + * when this structure is used in a hash key. */ +} Detail; + + +/* + * The following structure defines a pattern, which is matched against X + * events as part of the process of converting X events into TCL commands. + */ +typedef struct { + int eventType; /* Type of X event, e.g. ButtonPress. */ + int needMods; /* Mask of modifiers that must be + * present (0 means no modifiers are + * required). */ + Detail detail; /* Additional information that must + * match event. Normally this is 0, + * meaning no additional information + * must match. For KeyPress and + * KeyRelease events, a keySym may + * be specified to select a + * particular keystroke (0 means any + * keystrokes). For button events, + * specifies a particular button (0 + * means any buttons are OK). For virtual + * events, specifies the Tk_Uid of the + * virtual event name (never 0). */ +} Pattern; + +typedef struct { + const char *name; /* Name of modifier. */ + int mask; /* Button/modifier mask value, such as + * Button1Mask. */ + int flags; /* Various flags; see below for + * definitions. */ +} EventModifier; + +/* + * Flags for EventModifier structures: + * + * DOUBLE - Non-zero means duplicate this event, + * e.g. for double-clicks. + * TRIPLE - Non-zero means triplicate this event, + * e.g. for triple-clicks. + * QUADRUPLE - Non-zero means quadruple this event, + * e.g. for 4-fold-clicks. + * MULT_CLICKS - Combination of all of above. + */ + +#define DOUBLE (1<<0) +#define TRIPLE (1<<1) +#define QUADRUPLE (1<<2) +#define MULT_CLICKS (DOUBLE|TRIPLE|QUADRUPLE) + +#define META_MASK (AnyModifier<<1) +#define ALT_MASK (AnyModifier<<2) + +static EventModifier eventModifiers[] = { + {"Alt", ALT_MASK, 0}, + {"Any", 0, 0}, /* Ignored: historical relic. */ + {"B1", Button1Mask, 0}, + {"B2", Button2Mask, 0}, + {"B3", Button3Mask, 0}, + {"B4", Button4Mask, 0}, + {"B5", Button5Mask, 0}, + {"Button1", Button1Mask, 0}, + {"Button2", Button2Mask, 0}, + {"Button3", Button3Mask, 0}, + {"Button4", Button4Mask, 0}, + {"Button5", Button5Mask, 0}, + {"Command", Mod1Mask, 0}, + {"Control", ControlMask, 0}, + {"Double", 0, DOUBLE}, + {"Lock", LockMask, 0}, + {"M", META_MASK, 0}, + {"M1", Mod1Mask, 0}, + {"M2", Mod2Mask, 0}, + {"M3", Mod3Mask, 0}, + {"M4", Mod4Mask, 0}, + {"M5", Mod5Mask, 0}, + {"Meta", META_MASK, 0}, + {"Mod1", Mod1Mask, 0}, + {"Mod2", Mod2Mask, 0}, + {"Mod3", Mod3Mask, 0}, + {"Mod4", Mod4Mask, 0}, + {"Mod5", Mod5Mask, 0}, + {"Option", Mod2Mask, 0}, + {"Quadruple", 0, QUADRUPLE}, + {"Shift", ShiftMask, 0}, + {"Triple", 0, TRIPLE}, +}; + +typedef struct { + const char *name; /* Name of event. */ + int type; /* Event type for X, such as + * ButtonPress. */ + int eventMask; /* Mask bits (for XSelectInput) + * for this event type. */ +} EventInfo; + +/* + * Note: some of the masks below are an OR-ed combination of + * several masks. This is necessary because X doesn't report + * up events unless you also ask for down events. Also, X + * doesn't report button state in motion events unless you've + * asked about button events. + */ + +static EventInfo events[] = { + {"Activate", ActivateNotify, ActivateMask}, + {"Button", ButtonPress, ButtonPressMask}, + {"ButtonPress", ButtonPress, ButtonPressMask}, + {"ButtonRelease", ButtonRelease, ButtonPressMask|ButtonReleaseMask}, + {"Circulate", CirculateNotify, StructureNotifyMask}, + {"CirculateRequest", CirculateRequest, SubstructureRedirectMask}, + {"Colormap", ColormapNotify, ColormapChangeMask}, + {"Configure", ConfigureNotify, StructureNotifyMask}, + {"ConfigureRequest", ConfigureRequest, SubstructureRedirectMask}, + {"Create", CreateNotify, SubstructureNotifyMask}, + {"Deactivate", DeactivateNotify, ActivateMask}, + {"Destroy", DestroyNotify, StructureNotifyMask}, + {"Enter", EnterNotify, EnterWindowMask}, + {"Expose", Expose, ExposureMask}, + {"FocusIn", FocusIn, FocusChangeMask}, + {"FocusOut", FocusOut, FocusChangeMask}, + {"Gravity", GravityNotify, StructureNotifyMask}, + {"Key", KeyPress, KeyPressMask}, + {"KeyPress", KeyPress, KeyPressMask}, + {"KeyRelease", KeyRelease, KeyPressMask|KeyReleaseMask}, + {"Leave", LeaveNotify, LeaveWindowMask}, + {"Map", MapNotify, StructureNotifyMask}, + {"MapRequest", MapRequest, SubstructureRedirectMask}, + {"Motion", MotionNotify, ButtonPressMask|PointerMotionMask}, + {"MouseWheel", MouseWheelEvent, MouseWheelMask}, + {"Property", PropertyNotify, PropertyChangeMask}, + {"Reparent", ReparentNotify, StructureNotifyMask}, + {"ResizeRequest", ResizeRequest, ResizeRedirectMask}, + {"Unmap", UnmapNotify, StructureNotifyMask}, + {"Visibility", VisibilityNotify, VisibilityChangeMask}, +}; + +/* + * The defines and table below are used to classify events into + * various groups. The reason for this is that logically identical + * fields (e.g. "state") appear at different places in different + * types of events. The classification masks can be used to figure + * out quickly where to extract information from events. + */ + +#define KEY 0x1 +#define BUTTON 0x2 +#define MOTION 0x4 +#define CROSSING 0x8 +#define FOCUS 0x10 +#define EXPOSE 0x20 +#define VISIBILITY 0x40 +#define CREATE 0x80 +#define DESTROY 0x100 +#define UNMAP 0x200 +#define MAP 0x400 +#define REPARENT 0x800 +#define CONFIG 0x1000 +#define GRAVITY 0x2000 +#define CIRC 0x4000 +#define PROP 0x8000 +#define COLORMAP 0x10000 +#define VIRTUAL 0x20000 +#define ACTIVATE 0x40000 +#define MAPREQ 0x80000 +#define CONFIGREQ 0x100000 +#define RESIZEREQ 0x200000 +#define CIRCREQ 0x400000 + +#define KEY_BUTTON_MOTION_VIRTUAL (KEY|BUTTON|MOTION|VIRTUAL) +#define KEY_BUTTON_MOTION_CROSSING (KEY|BUTTON|MOTION|CROSSING|VIRTUAL) + +static int flagArray[TK_LASTEVENT+1] = { + /* Not used */ 0, + /* Not used */ 0, + /* KeyPress */ KEY, + /* KeyRelease */ KEY, + /* ButtonPress */ BUTTON, + /* ButtonRelease */ BUTTON, + /* MotionNotify */ MOTION, + /* EnterNotify */ CROSSING, + /* LeaveNotify */ CROSSING, + /* FocusIn */ FOCUS, + /* FocusOut */ FOCUS, + /* KeymapNotify */ 0, + /* Expose */ EXPOSE, + /* GraphicsExpose */ EXPOSE, + /* NoExpose */ 0, + /* VisibilityNotify */ VISIBILITY, + /* CreateNotify */ CREATE, + /* DestroyNotify */ DESTROY, + /* UnmapNotify */ UNMAP, + /* MapNotify */ MAP, + /* MapRequest */ MAPREQ, + /* ReparentNotify */ REPARENT, + /* ConfigureNotify */ CONFIG, + /* ConfigureRequest */ CONFIGREQ, + /* GravityNotify */ GRAVITY, + /* ResizeRequest */ RESIZEREQ, + /* CirculateNotify */ CIRC, + /* CirculateRequest */ 0, + /* PropertyNotify */ PROP, + /* SelectionClear */ 0, + /* SelectionRequest */ 0, + /* SelectionNotify */ 0, + /* ColormapNotify */ COLORMAP, + /* ClientMessage */ 0, + /* MappingNotify */ 0, +#ifdef GenericEvent + /* GenericEvent */ 0, +#endif + /* VirtualEvent */ VIRTUAL, + /* Activate */ ACTIVATE, + /* Deactivate */ ACTIVATE, + /* MouseWheel */ KEY +}; + + +static EventModifier * +FindModifier(const char *string) +{ + int high, low; + char c; + + low = 0; + high = (sizeof(eventModifiers) / sizeof(EventModifier)) - 1; + c = string[0]; + while (low <= high) { + EventModifier *modPtr; + int compare; + int median; + + median = (low + high) >> 1; + modPtr = eventModifiers + median; + + /* Test the first character */ + compare = c - modPtr->name[0]; + if (compare == 0) { + compare = strcmp(string, modPtr->name); + } + if (compare < 0) { + high = median - 1; + } else if (compare > 0) { + low = median + 1; + } else { + return modPtr; /* Modifier found. */ + } + } + return NULL; /* Can't find modifier */ +} + +static EventInfo * +FindEvent(const char *string) +{ + int high, low; + char c; + + low = 0; + high = (sizeof(events) / sizeof(EventInfo)) - 1; + c = string[0]; + while (low <= high) { + EventInfo *infoPtr; + int compare; + int median; + + median = (low + high) >> 1; + infoPtr = events + median; + + /* Test the first character */ + compare = c - infoPtr->name[0]; + if (compare == 0) { + compare = strcmp(string, infoPtr->name); + } + if (compare < 0) { + high = median - 1; + } else if (compare > 0) { + low = median + 1; + } else { + return infoPtr; /* Event found. */ + } + } + return NULL; /* Can't find event. */ +} + + +/* + *---------------------------------------------------------------------- + * + * GetField -- + * + * Used to parse pattern descriptions. Copies up to + * size characters from p to copy, stopping at end of + * string, space, "-", ">", or whenever size is + * exceeded. + * + * Results: + * The return value is a pointer to the character just + * after the last one copied (usually "-" or space or + * ">", but could be anything if size was exceeded). + * Also places NULL-terminated string (up to size + * character, including NULL), at copy. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static char * +GetField(p, copy, size) + char *p; /* Pointer to part of pattern. */ + char *copy; /* Place to copy field. */ + int size; /* Maximum number of characters to + * copy. */ +{ + while ((*p != '\0') && !isspace(UCHAR(*p)) && (*p != '>') + && (*p != '-') && (size > 1)) { + *copy = *p; + p++; + copy++; + size--; + } + *copy = '\0'; + return p; +} + +static int +ParseEventDescription(Tcl_Interp *interp, const char **eventStringPtr, + Pattern *patPtr, unsigned long *eventMaskPtr) +{ + char *p; + unsigned long eventMask; + int count, eventFlags; +#define FIELD_SIZE 48 + char field[FIELD_SIZE]; + EventInfo *infoPtr; + + Tcl_DString copy; + Tcl_DStringInit(©); + p = Tcl_DStringAppend(©, *eventStringPtr, -1); + + patPtr->eventType = -1; + patPtr->needMods = 0; + patPtr->detail.clientData = 0; + + eventMask = 0; + count = 1; + + /* + * Handle simple ASCII characters. + */ + + if (*p != '<') { + char string[2]; + + patPtr->eventType = KeyPress; + eventMask = KeyPressMask; + string[0] = *p; + string[1] = 0; + patPtr->detail.keySym = XStringToKeysym(string); + if (patPtr->detail.keySym == NoSymbol) { + if (isprint(UCHAR(*p))) { + patPtr->detail.keySym = *p; + } else { + char buf[64]; + + sprintf(buf, "bad ASCII character 0x%x", (unsigned char) *p); + Tcl_SetResult(interp, buf, TCL_VOLATILE); + count = 0; + goto done; + } + } + p++; + goto end; + } + + /* + * A physical event description consists of: + * + * 1. open angle bracket. + * 2. any number of modifiers, each followed by spaces + * or dashes. + * 3. an optional event name. + * 4. an option button or keysym name. Either this or + * item 3 *must* be present; if both are present + * then they are separated by spaces or dashes. + * 5. a close angle bracket. + */ + + p++; + + while (1) { + EventModifier *modPtr; + p = GetField(p, field, FIELD_SIZE); + if (*p == '>') { + /* + * This solves the problem of, e.g., being + * misinterpreted as Control + Meta + missing keysym + * instead of Control + KeyPress + M. + */ + break; + } + modPtr = FindModifier(field); + if (modPtr == NULL) { + break; + } + patPtr->needMods |= modPtr->mask; + if (modPtr->flags & (MULT_CLICKS)) { + int i = modPtr->flags & MULT_CLICKS; + count = 2; + while (i >>= 1) count++; + } + while ((*p == '-') || isspace(UCHAR(*p))) { + p++; + } + } + + eventFlags = 0; + infoPtr = FindEvent(field); + if (infoPtr != NULL) { + patPtr->eventType = infoPtr->type; + eventFlags = flagArray[infoPtr->type]; + eventMask = infoPtr->eventMask; + while ((*p == '-') || isspace(UCHAR(*p))) { + p++; + } + p = GetField(p, field, FIELD_SIZE); + } + if (*field != '\0') { + if ((*field >= '1') && (*field <= '5') && (field[1] == '\0')) { + if (eventFlags == 0) { + patPtr->eventType = ButtonPress; + eventMask = ButtonPressMask; + } else if (eventFlags & KEY) { + goto getKeysym; + } else if ((eventFlags & BUTTON) == 0) { + Tcl_AppendResult(interp, "specified button \"", field, + "\" for non-button event", (char *) NULL); + count = 0; + goto done; + } + patPtr->detail.button = (*field - '0'); + } else { + getKeysym: + patPtr->detail.keySym = XStringToKeysym(field); + if (patPtr->detail.keySym == NoSymbol) { + Tcl_AppendResult(interp, "bad event type or keysym \"", + field, "\"", (char *)NULL); + count = 0; + goto done; + } + if (eventFlags == 0) { + patPtr->eventType = KeyPress; + eventMask = KeyPressMask; + } else if ((eventFlags & KEY) == 0) { + Tcl_AppendResult(interp, "specified keysym \"", field, + "\" for non-key event", (char *)NULL); + count = 0; + goto done; + } + } + } else if (eventFlags == 0) { + Tcl_AppendResult(interp, "no event type or button # or keysym", + (char *)NULL); + count = 0; + goto done; + } + + while ((*p == '-') || isspace(UCHAR(*p))) { + p++; + } + if (*p != '>') { + while (*p != '\0') { + p++; + if (*p == '>') { + Tcl_AppendResult(interp, + "extra characters after detail in binding", + (char *)NULL); + count = 0; + goto done; + } + } + Tcl_AppendResult(interp, "missing \">\" in binding", (char *)NULL); + count = 0; + goto done; + } + p++; + +end: + *eventStringPtr += (p - Tcl_DStringValue(©)); + *eventMaskPtr |= eventMask; +done: + Tcl_DStringFree(©); + return count; +} + +typedef struct { + int numKey; /* Integer representation of a value. */ + const char *strKey; /* String representation of a value. */ +} TkStateMap; + +static TkStateMap notifyMode[] = { + {NotifyNormal, "NotifyNormal"}, + {NotifyGrab, "NotifyGrab"}, + {NotifyUngrab, "NotifyUngrab"}, + {NotifyWhileGrabbed, "NotifyWhileGrabbed"}, + {-1, NULL} +}; + +static TkStateMap notifyDetail[] = { + {NotifyAncestor, "NotifyAncestor"}, + {NotifyVirtual, "NotifyVirtual"}, + {NotifyInferior, "NotifyInferior"}, + {NotifyNonlinear, "NotifyNonlinear"}, + {NotifyNonlinearVirtual, "NotifyNonlinearVirtual"}, + {NotifyPointer, "NotifyPointer"}, + {NotifyPointerRoot, "NotifyPointerRoot"}, + {NotifyDetailNone, "NotifyDetailNone"}, + {-1, NULL} +}; + +static TkStateMap circPlace[] = { + {PlaceOnTop, "PlaceOnTop"}, + {PlaceOnBottom, "PlaceOnBottom"}, + {-1, NULL} +}; + +static TkStateMap visNotify[] = { + {VisibilityUnobscured, "VisibilityUnobscured"}, + {VisibilityPartiallyObscured, "VisibilityPartiallyObscured"}, + {VisibilityFullyObscured, "VisibilityFullyObscured"}, + {-1, NULL} +}; + +static TkStateMap configureRequestDetail[] = { + {None, "None"}, + {Above, "Above"}, + {Below, "Below"}, + {BottomIf, "BottomIf"}, + {TopIf, "TopIf"}, + {Opposite, "Opposite"}, + {-1, NULL} +}; + +static TkStateMap propNotify[] = { + {PropertyNewValue, "NewValue"}, + {PropertyDelete, "Delete"}, + {-1, NULL} +}; + +/* + *--------------------------------------------------------------------------- + * + * HandleEventGenerate -- + * + * Helper function for the "event generate" command. Generate and + * process an XEvent, constructed from information parsed from the + * event description string and its optional arguments. + * + * argv[0] contains name of the target window. + * argv[1] contains pattern string for one event (e.g, ). + * argv[2..argc-1] contains -field/option pairs for specifying + * additional detail in the generated event. + * + * Either virtual or physical events can be generated this way. + * The event description string must contain the specification + * for only one event. + * + * Results: + * None. + * + * Side effects: + * When constructing the event, + * event.xany.serial is filled with the current X serial number. + * event.xany.window is filled with the target window. + * event.xany.display is filled with the target window's display. + * Any other fields in eventPtr which are not specified by the pattern + * string or the optional arguments, are set to 0. + * + * The event may be handled sychronously or asynchronously, depending + * on the value specified by the optional "-when" option. The + * default setting is synchronous. + * + *--------------------------------------------------------------------------- + */ +static int +SendEventCmd(ClientData clientData, Tcl_Interp *interp, int objc, + Tcl_Obj *const *objv) +{ + XEvent event; + CONST char *p; + char *name; + Window window; + Display *display; + Tk_Window tkwin; + int count, flags, synch, i, number, warp; + Tcl_QueuePosition pos; + Pattern pat; + unsigned long eventMask; + static CONST char *fieldStrings[] = { + "-when", "-above", "-borderwidth", "-button", + "-count", "-delta", "-detail", "-focus", + "-height", + "-keycode", "-keysym", "-mode", "-override", + "-place", "-root", "-rootx", "-rooty", + "-sendevent", "-serial", "-state", "-subwindow", + "-time", "-warp", "-width", "-window", + "-x", "-y", NULL + }; + enum field { + EVENT_WHEN, EVENT_ABOVE, EVENT_BORDER, EVENT_BUTTON, + EVENT_COUNT, EVENT_DELTA, EVENT_DETAIL, EVENT_FOCUS, + EVENT_HEIGHT, + EVENT_KEYCODE, EVENT_KEYSYM, EVENT_MODE, EVENT_OVERRIDE, + EVENT_PLACE, EVENT_ROOT, EVENT_ROOTX, EVENT_ROOTY, + EVENT_SEND, EVENT_SERIAL, EVENT_STATE, EVENT_SUBWINDOW, + EVENT_TIME, EVENT_WARP, EVENT_WIDTH, EVENT_WINDOW, + EVENT_X, EVENT_Y + }; + tkwin = Tk_MainWindow(interp); + if (Blt_GetWindowFromObj(interp, objv[1], &window) != TCL_OK) { + return TCL_ERROR; + } + name = Tcl_GetStringFromObj(objv[2], NULL); + + display = Tk_Display(tkwin); + p = name; + eventMask = 0; + count = ParseEventDescription(interp, &p, &pat, &eventMask); + if (count == 0) { + return TCL_ERROR; + } + if (count != 1) { + Tcl_AppendResult(interp, "Double or Triple modifier not allowed", + (char *)NULL); + return TCL_ERROR; + } + if (*p != '\0') { + Tcl_AppendResult(interp, "only one event specification allowed", + (char *)NULL); + return TCL_ERROR; + } + + memset((VOID *) &event, 0, sizeof(event)); + event.xany.type = pat.eventType; + event.xany.serial = NextRequest(display); + event.xany.send_event = False; + event.xany.window = window; + event.xany.display = display; + + flags = flagArray[event.xany.type]; + if (flags & DESTROY) { + /* + * Event DestroyNotify should be generated by destroying + * the window. + */ + XDestroyWindow(display, window); + return TCL_OK; + } + if (flags & (KEY_BUTTON_MOTION_VIRTUAL)) { + event.xkey.state = pat.needMods; + if ((flags & KEY) && (event.xany.type != MouseWheelEvent)) { + TkpSetKeycodeAndState(tkwin, pat.detail.keySym, &event); + } else if (flags & BUTTON) { + event.xbutton.button = pat.detail.button; + } else if (flags & VIRTUAL) { + ((XVirtualEvent *) &event)->name = pat.detail.name; + } + } + if (flags & (CREATE|UNMAP|MAP|REPARENT|CONFIG|GRAVITY|CIRC)) { + event.xcreatewindow.window = event.xany.window; + } + + if (flags & (KEY_BUTTON_MOTION_VIRTUAL|CROSSING)) { + event.xkey.x_root = -1; + event.xkey.y_root = -1; + } + + /* + * Process the remaining arguments to fill in additional fields + * of the event. + */ + + synch = 1; + warp = 0; + pos = TCL_QUEUE_TAIL; + for (i = 3; i < objc; i += 2) { + Tcl_Obj *optionPtr, *valuePtr; + int index; + + optionPtr = objv[i]; + valuePtr = objv[i + 1]; + + if (Tcl_GetIndexFromObj(interp, optionPtr, fieldStrings, "option", + TCL_EXACT, &index) != TCL_OK) { + return TCL_ERROR; + } + if ((objc & 1) == 0) { + /* + * This test occurs after Tcl_GetIndexFromObj() so that + * "event generate