1/*
2 * tkSquare.c --
3 *
4 *	This module implements "square" widgets that are object based. A
5 *	"square" is a widget that displays a single square that can be moved
6 *	around and resized. This file is intended as an example of how to
7 *	build a widget; it isn't included in the normal wish, but it is
8 *	included in "tktest".
9 *
10 * Copyright (c) 1997 Sun Microsystems, Inc.
11 *
12 * See the file "license.terms" for information on usage and redistribution of
13 * this file, and for a DISCLAIMER OF ALL WARRANTIES.
14 *
15 * RCS: @(#) $Id$
16 */
17
18#if 0
19#define __NO_OLD_CONFIG
20#endif
21#include "tkInt.h"
22
23/*
24 * A data structure of the following type is kept for each square widget
25 * managed by this file:
26 */
27
28typedef struct {
29    Tk_Window tkwin;		/* Window that embodies the square. NULL means
30				 * window has been deleted but widget record
31				 * hasn't been cleaned up yet. */
32    Display *display;		/* X's token for the window's display. */
33    Tcl_Interp *interp;		/* Interpreter associated with widget. */
34    Tcl_Command widgetCmd;	/* Token for square's widget command. */
35    Tk_OptionTable optionTable;	/* Token representing the configuration
36				 * specifications. */
37    Tcl_Obj *xPtr, *yPtr;	/* Position of square's upper-left corner
38				 * within widget. */
39    int x, y;
40    Tcl_Obj *sizeObjPtr;	/* Width and height of square. */
41
42    /*
43     * Information used when displaying widget:
44     */
45
46    Tcl_Obj *borderWidthPtr;	/* Width of 3-D border around whole widget. */
47    Tcl_Obj *bgBorderPtr;
48    Tcl_Obj *fgBorderPtr;
49    Tcl_Obj *reliefPtr;
50    GC gc;			/* Graphics context for copying from
51				 * off-screen pixmap onto screen. */
52    Tcl_Obj *doubleBufferPtr;	/* Non-zero means double-buffer redisplay with
53				 * pixmap; zero means draw straight onto the
54				 * display. */
55    int updatePending;		/* Non-zero means a call to SquareDisplay has
56				 * already been scheduled. */
57} Square;
58
59/*
60 * Information used for argv parsing.
61 */
62
63static const Tk_OptionSpec optionSpecs[] = {
64    {TK_OPTION_BORDER, "-background", "background", "Background",
65	    "#d9d9d9", Tk_Offset(Square, bgBorderPtr), -1, 0,
66	    (ClientData) "white"},
67    {TK_OPTION_SYNONYM, "-bd", NULL, NULL, NULL, 0, -1, 0,
68	    (ClientData) "-borderwidth"},
69    {TK_OPTION_SYNONYM, "-bg", NULL, NULL, NULL, 0, -1, 0,
70	    (ClientData) "-background"},
71    {TK_OPTION_PIXELS, "-borderwidth", "borderWidth", "BorderWidth",
72	    "2", Tk_Offset(Square, borderWidthPtr), -1},
73    {TK_OPTION_BOOLEAN, "-dbl", "doubleBuffer", "DoubleBuffer",
74	    "1", Tk_Offset(Square, doubleBufferPtr), -1},
75    {TK_OPTION_SYNONYM, "-fg", NULL, NULL, NULL, 0, -1, 0,
76	    (ClientData) "-foreground"},
77    {TK_OPTION_BORDER, "-foreground", "foreground", "Foreground",
78	    "#b03060", Tk_Offset(Square, fgBorderPtr), -1, 0,
79	    (ClientData) "black"},
80    {TK_OPTION_PIXELS, "-posx", "posx", "PosX", "0",
81	    Tk_Offset(Square, xPtr), -1},
82    {TK_OPTION_PIXELS, "-posy", "posy", "PosY", "0",
83	    Tk_Offset(Square, yPtr), -1},
84    {TK_OPTION_RELIEF, "-relief", "relief", "Relief",
85	    "raised", Tk_Offset(Square, reliefPtr), -1},
86    {TK_OPTION_PIXELS, "-size", "size", "Size", "20",
87	    Tk_Offset(Square, sizeObjPtr), -1},
88    {TK_OPTION_END}
89};
90
91/*
92 * Forward declarations for procedures defined later in this file:
93 */
94
95int			SquareObjCmd(ClientData clientData,
96			    Tcl_Interp *interp, int objc,
97			    Tcl_Obj * CONST objv[]);
98static void		SquareDeletedProc(ClientData clientData);
99static int		SquareConfigure(Tcl_Interp *interp, Square *squarePtr);
100static void		SquareDestroy(char *memPtr);
101static void		SquareDisplay(ClientData clientData);
102static void		KeepInWindow(Square *squarePtr);
103static void		SquareObjEventProc(ClientData clientData,
104			    XEvent *eventPtr);
105static int		SquareWidgetObjCmd(ClientData clientData,
106			    Tcl_Interp *, int objc, Tcl_Obj * CONST objv[]);
107
108/*
109 *--------------------------------------------------------------
110 *
111 * SquareCmd --
112 *
113 *	This procedure is invoked to process the "square" Tcl command. It
114 *	creates a new "square" widget.
115 *
116 * Results:
117 *	A standard Tcl result.
118 *
119 * Side effects:
120 *	A new widget is created and configured.
121 *
122 *--------------------------------------------------------------
123 */
124
125int
126SquareObjCmd(
127    ClientData clientData,	/* NULL. */
128    Tcl_Interp *interp,		/* Current interpreter. */
129    int objc,			/* Number of arguments. */
130    Tcl_Obj *CONST objv[])	/* Argument objects. */
131{
132    Square *squarePtr;
133    Tk_Window tkwin;
134    Tk_OptionTable optionTable;
135
136    if (objc < 2) {
137	Tcl_WrongNumArgs(interp, 1, objv, "pathName ?options?");
138	return TCL_ERROR;
139    }
140
141    tkwin = Tk_CreateWindowFromPath(interp, Tk_MainWindow(interp),
142	    Tcl_GetString(objv[1]), NULL);
143    if (tkwin == NULL) {
144	return TCL_ERROR;
145    }
146    Tk_SetClass(tkwin, "Square");
147
148    /*
149     * Create the option table for this widget class. If it has already been
150     * created, the refcount will get bumped and just the pointer will be
151     * returned. The refcount getting bumped does not concern us, because Tk
152     * will ensure the table is deleted when the interpreter is destroyed.
153     */
154
155    optionTable = Tk_CreateOptionTable(interp, optionSpecs);
156
157    /*
158     * Allocate and initialize the widget record. The memset allows us to set
159     * just the non-NULL/0 items.
160     */
161
162    squarePtr = (Square *) ckalloc(sizeof(Square));
163    memset((void *) squarePtr, 0, (sizeof(Square)));
164
165    squarePtr->tkwin = tkwin;
166    squarePtr->display = Tk_Display(tkwin);
167    squarePtr->interp = interp;
168    squarePtr->widgetCmd = Tcl_CreateObjCommand(interp,
169	    Tk_PathName(squarePtr->tkwin), SquareWidgetObjCmd,
170	    (ClientData) squarePtr, SquareDeletedProc);
171    squarePtr->gc = None;
172    squarePtr->optionTable = optionTable;
173
174    if (Tk_InitOptions(interp, (char *) squarePtr, optionTable, tkwin)
175	    != TCL_OK) {
176	Tk_DestroyWindow(squarePtr->tkwin);
177	ckfree((char *) squarePtr);
178	return TCL_ERROR;
179    }
180
181    Tk_CreateEventHandler(squarePtr->tkwin, ExposureMask|StructureNotifyMask,
182	    SquareObjEventProc, (ClientData) squarePtr);
183    if (Tk_SetOptions(interp, (char *) squarePtr, optionTable, objc - 2,
184	    objv + 2, tkwin, NULL, NULL) != TCL_OK) {
185	goto error;
186    }
187    if (SquareConfigure(interp, squarePtr) != TCL_OK) {
188	goto error;
189    }
190
191    Tcl_SetObjResult(interp,
192	    Tcl_NewStringObj(Tk_PathName(squarePtr->tkwin), -1));
193    return TCL_OK;
194
195  error:
196    Tk_DestroyWindow(squarePtr->tkwin);
197    return TCL_ERROR;
198}
199
200/*
201 *--------------------------------------------------------------
202 *
203 * SquareWidgetObjCmd --
204 *
205 *	This procedure is invoked to process the Tcl command that corresponds
206 *	to a widget managed by this module. See the user documentation for
207 *	details on what it does.
208 *
209 * Results:
210 *	A standard Tcl result.
211 *
212 * Side effects:
213 *	See the user documentation.
214 *
215 *--------------------------------------------------------------
216 */
217
218static int
219SquareWidgetObjCmd(
220    ClientData clientData,	/* Information about square widget. */
221    Tcl_Interp *interp,		/* Current interpreter. */
222    int objc,			/* Number of arguments. */
223    Tcl_Obj * CONST objv[])	/* Argument objects. */
224{
225    Square *squarePtr = (Square *) clientData;
226    int result = TCL_OK;
227    static CONST char *squareOptions[] = {"cget", "configure", NULL};
228    enum {
229	SQUARE_CGET, SQUARE_CONFIGURE
230    };
231    Tcl_Obj *resultObjPtr;
232    int index;
233
234    if (objc < 2) {
235	Tcl_WrongNumArgs(interp, 1, objv, "option ?arg arg...?");
236	return TCL_ERROR;
237    }
238
239    if (Tcl_GetIndexFromObj(interp, objv[1], squareOptions, "command",
240	    0, &index) != TCL_OK) {
241	return TCL_ERROR;
242    }
243
244    Tcl_Preserve((ClientData) squarePtr);
245
246    switch (index) {
247    case SQUARE_CGET:
248	if (objc != 3) {
249	    Tcl_WrongNumArgs(interp, 2, objv, "option");
250	    goto error;
251	}
252	resultObjPtr = Tk_GetOptionValue(interp, (char *) squarePtr,
253		squarePtr->optionTable, objv[2], squarePtr->tkwin);
254	if (resultObjPtr == NULL) {
255	    result = TCL_ERROR;
256	} else {
257	    Tcl_SetObjResult(interp, resultObjPtr);
258	}
259	break;
260    case SQUARE_CONFIGURE:
261	resultObjPtr = NULL;
262	if (objc == 2) {
263	    resultObjPtr = Tk_GetOptionInfo(interp, (char *) squarePtr,
264		    squarePtr->optionTable, NULL, squarePtr->tkwin);
265	    if (resultObjPtr == NULL) {
266		result = TCL_ERROR;
267	    }
268	} else if (objc == 3) {
269	    resultObjPtr = Tk_GetOptionInfo(interp, (char *) squarePtr,
270		    squarePtr->optionTable, objv[2], squarePtr->tkwin);
271	    if (resultObjPtr == NULL) {
272		result = TCL_ERROR;
273	    }
274	} else {
275	    result = Tk_SetOptions(interp, (char *) squarePtr,
276		    squarePtr->optionTable, objc - 2, objv + 2,
277		    squarePtr->tkwin, NULL, NULL);
278	    if (result == TCL_OK) {
279		result = SquareConfigure(interp, squarePtr);
280	    }
281	    if (!squarePtr->updatePending) {
282		Tcl_DoWhenIdle(SquareDisplay, (ClientData) squarePtr);
283		squarePtr->updatePending = 1;
284	    }
285	}
286	if (resultObjPtr != NULL) {
287	    Tcl_SetObjResult(interp, resultObjPtr);
288	}
289    }
290    Tcl_Release((ClientData) squarePtr);
291    return result;
292
293  error:
294    Tcl_Release((ClientData) squarePtr);
295    return TCL_ERROR;
296}
297
298/*
299 *----------------------------------------------------------------------
300 *
301 * SquareConfigure --
302 *
303 *	This procedure is called to process an argv/argc list in conjunction
304 *	with the Tk option database to configure (or reconfigure) a square
305 *	widget.
306 *
307 * Results:
308 *	The return value is a standard Tcl result. If TCL_ERROR is returned,
309 *	then the interp's result contains an error message.
310 *
311 * Side effects:
312 *	Configuration information, such as colors, border width, etc. get set
313 *	for squarePtr; old resources get freed, if there were any.
314 *
315 *----------------------------------------------------------------------
316 */
317
318static int
319SquareConfigure(
320    Tcl_Interp *interp,		/* Used for error reporting. */
321    Square *squarePtr)		/* Information about widget. */
322{
323    int borderWidth;
324    Tk_3DBorder bgBorder;
325    int doubleBuffer;
326
327    /*
328     * Set the background for the window and create a graphics context for use
329     * during redisplay.
330     */
331
332    bgBorder = Tk_Get3DBorderFromObj(squarePtr->tkwin,
333	    squarePtr->bgBorderPtr);
334    Tk_SetWindowBackground(squarePtr->tkwin,
335	    Tk_3DBorderColor(bgBorder)->pixel);
336    Tcl_GetBooleanFromObj(NULL, squarePtr->doubleBufferPtr, &doubleBuffer);
337    if ((squarePtr->gc == None) && (doubleBuffer)) {
338	XGCValues gcValues;
339	gcValues.function = GXcopy;
340	gcValues.graphics_exposures = False;
341	squarePtr->gc = Tk_GetGC(squarePtr->tkwin,
342		GCFunction|GCGraphicsExposures, &gcValues);
343    }
344
345    /*
346     * Register the desired geometry for the window. Then arrange for the
347     * window to be redisplayed.
348     */
349
350    Tk_GeometryRequest(squarePtr->tkwin, 200, 150);
351    Tk_GetPixelsFromObj(NULL, squarePtr->tkwin, squarePtr->borderWidthPtr,
352	    &borderWidth);
353    Tk_SetInternalBorder(squarePtr->tkwin, borderWidth);
354    if (!squarePtr->updatePending) {
355	Tcl_DoWhenIdle(SquareDisplay, (ClientData) squarePtr);
356	squarePtr->updatePending = 1;
357    }
358    KeepInWindow(squarePtr);
359    return TCL_OK;
360}
361
362/*
363 *--------------------------------------------------------------
364 *
365 * SquareObjEventProc --
366 *
367 *	This procedure is invoked by the Tk dispatcher for various events on
368 *	squares.
369 *
370 * Results:
371 *	None.
372 *
373 * Side effects:
374 *	When the window gets deleted, internal structures get cleaned up. When
375 *	it gets exposed, it is redisplayed.
376 *
377 *--------------------------------------------------------------
378 */
379
380static void
381SquareObjEventProc(
382    ClientData clientData,	/* Information about window. */
383    XEvent *eventPtr)		/* Information about event. */
384{
385    Square *squarePtr = (Square *) clientData;
386
387    if (eventPtr->type == Expose) {
388	if (!squarePtr->updatePending) {
389	    Tcl_DoWhenIdle(SquareDisplay, (ClientData) squarePtr);
390	    squarePtr->updatePending = 1;
391	}
392    } else if (eventPtr->type == ConfigureNotify) {
393	KeepInWindow(squarePtr);
394	if (!squarePtr->updatePending) {
395	    Tcl_DoWhenIdle(SquareDisplay, (ClientData) squarePtr);
396	    squarePtr->updatePending = 1;
397	}
398    } else if (eventPtr->type == DestroyNotify) {
399	if (squarePtr->tkwin != NULL) {
400	    Tk_FreeConfigOptions((char *) squarePtr, squarePtr->optionTable,
401		    squarePtr->tkwin);
402	    if (squarePtr->gc != None) {
403		Tk_FreeGC(squarePtr->display, squarePtr->gc);
404	    }
405	    squarePtr->tkwin = NULL;
406	    Tcl_DeleteCommandFromToken(squarePtr->interp,
407		    squarePtr->widgetCmd);
408	}
409	if (squarePtr->updatePending) {
410	    Tcl_CancelIdleCall(SquareDisplay, (ClientData) squarePtr);
411	}
412	Tcl_EventuallyFree((ClientData) squarePtr, SquareDestroy);
413    }
414}
415
416/*
417 *----------------------------------------------------------------------
418 *
419 * SquareDeletedProc --
420 *
421 *	This procedure is invoked when a widget command is deleted. If the
422 *	widget isn't already in the process of being destroyed, this command
423 *	destroys it.
424 *
425 * Results:
426 *	None.
427 *
428 * Side effects:
429 *	The widget is destroyed.
430 *
431 *----------------------------------------------------------------------
432 */
433
434static void
435SquareDeletedProc(
436    ClientData clientData)	/* Pointer to widget record for widget. */
437{
438    Square *squarePtr = (Square *) clientData;
439    Tk_Window tkwin = squarePtr->tkwin;
440
441    /*
442     * This procedure could be invoked either because the window was destroyed
443     * and the command was then deleted (in which case tkwin is NULL) or
444     * because the command was deleted, and then this procedure destroys the
445     * widget.
446     */
447
448    if (tkwin != NULL) {
449	Tk_DestroyWindow(tkwin);
450    }
451}
452
453/*
454 *--------------------------------------------------------------
455 *
456 * SquareDisplay --
457 *
458 *	This procedure redraws the contents of a square window. It is invoked
459 *	as a do-when-idle handler, so it only runs when there's nothing else
460 *	for the application to do.
461 *
462 * Results:
463 *	None.
464 *
465 * Side effects:
466 *	Information appears on the screen.
467 *
468 *--------------------------------------------------------------
469 */
470
471static void
472SquareDisplay(
473    ClientData clientData)	/* Information about window. */
474{
475    Square *squarePtr = (Square *) clientData;
476    Tk_Window tkwin = squarePtr->tkwin;
477    Pixmap pm = None;
478    Drawable d;
479    int borderWidth, size, relief;
480    Tk_3DBorder bgBorder, fgBorder;
481    int doubleBuffer;
482
483    squarePtr->updatePending = 0;
484    if (!Tk_IsMapped(tkwin)) {
485	return;
486    }
487
488    /*
489     * Create a pixmap for double-buffering, if necessary.
490     */
491
492    Tcl_GetBooleanFromObj(NULL, squarePtr->doubleBufferPtr, &doubleBuffer);
493    if (doubleBuffer) {
494	pm = Tk_GetPixmap(Tk_Display(tkwin), Tk_WindowId(tkwin),
495		Tk_Width(tkwin), Tk_Height(tkwin),
496		DefaultDepthOfScreen(Tk_Screen(tkwin)));
497	d = pm;
498    } else {
499	d = Tk_WindowId(tkwin);
500    }
501
502    /*
503     * Redraw the widget's background and border.
504     */
505
506    Tk_GetPixelsFromObj(NULL, squarePtr->tkwin, squarePtr->borderWidthPtr,
507	    &borderWidth);
508    bgBorder = Tk_Get3DBorderFromObj(squarePtr->tkwin,
509	    squarePtr->bgBorderPtr);
510    Tk_GetReliefFromObj(NULL, squarePtr->reliefPtr, &relief);
511    Tk_Fill3DRectangle(tkwin, d, bgBorder, 0, 0, Tk_Width(tkwin),
512	    Tk_Height(tkwin), borderWidth, relief);
513
514    /*
515     * Display the square.
516     */
517
518    Tk_GetPixelsFromObj(NULL, squarePtr->tkwin, squarePtr->sizeObjPtr, &size);
519    fgBorder = Tk_Get3DBorderFromObj(squarePtr->tkwin,
520	    squarePtr->fgBorderPtr);
521    Tk_Fill3DRectangle(tkwin, d, fgBorder, squarePtr->x, squarePtr->y, size,
522	    size, borderWidth, TK_RELIEF_RAISED);
523
524    /*
525     * If double-buffered, copy to the screen and release the pixmap.
526     */
527
528    if (doubleBuffer) {
529	XCopyArea(Tk_Display(tkwin), pm, Tk_WindowId(tkwin), squarePtr->gc,
530		0, 0, (unsigned) Tk_Width(tkwin), (unsigned) Tk_Height(tkwin),
531		0, 0);
532	Tk_FreePixmap(Tk_Display(tkwin), pm);
533    }
534}
535
536/*
537 *----------------------------------------------------------------------
538 *
539 * SquareDestroy --
540 *
541 *	This procedure is invoked by Tcl_EventuallyFree or Tcl_Release to
542 *	clean up the internal structure of a square at a safe time (when
543 *	no-one is using it anymore).
544 *
545 * Results:
546 *	None.
547 *
548 * Side effects:
549 *	Everything associated with the square is freed up.
550 *
551 *----------------------------------------------------------------------
552 */
553
554static void
555SquareDestroy(
556    char *memPtr)		/* Info about square widget. */
557{
558    Square *squarePtr = (Square *) memPtr;
559
560    ckfree((char *) squarePtr);
561}
562
563/*
564 *----------------------------------------------------------------------
565 *
566 * KeepInWindow --
567 *
568 *	Adjust the position of the square if necessary to keep it in the
569 *	widget's window.
570 *
571 * Results:
572 *	None.
573 *
574 * Side effects:
575 *	The x and y position of the square are adjusted if necessary to keep
576 *	the square in the window.
577 *
578 *----------------------------------------------------------------------
579 */
580
581static void
582KeepInWindow(
583    register Square *squarePtr)	/* Pointer to widget record. */
584{
585    int i, bd, relief;
586    int borderWidth, size;
587
588    Tk_GetPixelsFromObj(NULL, squarePtr->tkwin, squarePtr->borderWidthPtr,
589	    &borderWidth);
590    Tk_GetPixelsFromObj(NULL, squarePtr->tkwin, squarePtr->xPtr,
591	    &squarePtr->x);
592    Tk_GetPixelsFromObj(NULL, squarePtr->tkwin, squarePtr->yPtr,
593	    &squarePtr->y);
594    Tk_GetPixelsFromObj(NULL, squarePtr->tkwin, squarePtr->sizeObjPtr, &size);
595    Tk_GetReliefFromObj(NULL, squarePtr->reliefPtr, &relief);
596    bd = 0;
597    if (relief != TK_RELIEF_FLAT) {
598	bd = borderWidth;
599    }
600    i = (Tk_Width(squarePtr->tkwin) - bd) - (squarePtr->x + size);
601    if (i < 0) {
602	squarePtr->x += i;
603    }
604    i = (Tk_Height(squarePtr->tkwin) - bd) - (squarePtr->y + size);
605    if (i < 0) {
606	squarePtr->y += i;
607    }
608    if (squarePtr->x < bd) {
609	squarePtr->x = bd;
610    }
611    if (squarePtr->y < bd) {
612	squarePtr->y = bd;
613    }
614}
615
616/*
617 * Local Variables:
618 * mode: c
619 * c-basic-offset: 4
620 * fill-column: 78
621 * End:
622 */
623