Command.c revision 994689c1
1/***********************************************************
2
3Copyright 1987, 1988, 1994, 1998  The Open Group
4
5Permission to use, copy, modify, distribute, and sell this software and its
6documentation for any purpose is hereby granted without fee, provided that
7the above copyright notice appear in all copies and that both that
8copyright notice and this permission notice appear in supporting
9documentation.
10
11The above copyright notice and this permission notice shall be included in
12all copies or substantial portions of the Software.
13
14THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
17OPEN GROUP BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
18AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
19CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20
21Except as contained in this notice, the name of The Open Group shall not be
22used in advertising or otherwise to promote the sale, use or other dealings
23in this Software without prior written authorization from The Open Group.
24
25
26Copyright 1987, 1988 by Digital Equipment Corporation, Maynard, Massachusetts.
27
28                        All Rights Reserved
29
30Permission to use, copy, modify, and distribute this software and its
31documentation for any purpose and without fee is hereby granted,
32provided that the above copyright notice appear in all copies and that
33both that copyright notice and this permission notice appear in
34supporting documentation, and that the name of Digital not be
35used in advertising or publicity pertaining to distribution of the
36software without specific, written prior permission.
37
38DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
39ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
40DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
41ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
42WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
43ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
44SOFTWARE.
45
46******************************************************************/
47
48/*
49 * Command.c - Command button widget
50 */
51
52#ifdef HAVE_CONFIG_H
53#include <config.h>
54#endif
55#include <stdio.h>
56#include <X11/IntrinsicP.h>
57#include <X11/StringDefs.h>
58#include <X11/extensions/shape.h>
59#include <X11/Xmu/Converters.h>
60#include <X11/Xmu/Drawing.h>
61#include <X11/Xmu/Misc.h>
62#include <X11/Xaw/CommandP.h>
63#include <X11/Xaw/XawInit.h>
64#include "Private.h"
65
66#define DEFAULT_HIGHLIGHT_THICKNESS 2
67#define DEFAULT_SHAPE_HIGHLIGHT 32767
68#define STR_EQUAL(str1, str2)	(str1 == str2 || strcmp(str1, str2) == 0)
69
70/*
71 * Class Methods
72 */
73static void XawCommandClassInitialize(void);
74static void XawCommandDestroy(Widget);
75static void XawCommandInitialize(Widget, Widget, ArgList, Cardinal*);
76static void XawCommandRealize(Widget, Mask*, XSetWindowAttributes*);
77static void XawCommandResize(Widget);
78static void XawCommandRedisplay(Widget, XEvent*, Region);
79static Boolean XawCommandSetValues(Widget, Widget, Widget, ArgList, Cardinal*);
80static void XawCommandGetValuesHook(Widget, ArgList, Cardinal*);
81static Bool ChangeSensitive(Widget);
82
83/*
84 * Prototypes
85 */
86static GC Get_GC(CommandWidget, Pixel, Pixel);
87static void PaintCommandWidget(Widget, XEvent*, Region, Bool);
88static Region HighlightRegion(CommandWidget);
89static Bool ShapeButton(CommandWidget, Bool);
90static void XawCommandToggle(Widget);
91
92/*
93 * Actions
94 */
95static void Highlight(Widget, XEvent*, String*, Cardinal*);
96static void Notify(Widget, XEvent*, String*, Cardinal*);
97static void Reset(Widget, XEvent*, String*, Cardinal*);
98static void Set(Widget, XEvent*, String*, Cardinal*);
99static void Unhighlight(Widget, XEvent*, String*, Cardinal*);
100static void Unset(Widget, XEvent*, String*, Cardinal*);
101
102/*
103 * Initialization
104 */
105static char defaultTranslations[] =
106"<Enter>:"	"highlight()\n"
107"<Leave>:"	"reset()\n"
108"<Btn1Down>:"	"set()\n"
109"<Btn1Up>:"	"notify() unset()\n"
110;
111
112#define offset(field) XtOffsetOf(CommandRec, field)
113static XtResource resources[] = {
114  {
115    XtNcallback,
116    XtCCallback,
117    XtRCallback,
118    sizeof(XtPointer),
119    offset(command.callbacks),
120    XtRCallback,
121    NULL
122  },
123  {
124    XtNhighlightThickness,
125    XtCThickness,
126    XtRDimension,
127    sizeof(Dimension),
128    offset(command.highlight_thickness),
129    XtRImmediate,
130    (XtPointer)DEFAULT_SHAPE_HIGHLIGHT
131  },
132  {
133    XtNshapeStyle,
134    XtCShapeStyle,
135    XtRShapeStyle,
136    sizeof(int),
137    offset(command.shape_style),
138    XtRImmediate,
139    (XtPointer)XawShapeRectangle
140  },
141  {
142    XtNcornerRoundPercent,
143    XtCCornerRoundPercent,
144    XtRDimension,
145    sizeof(Dimension),
146    offset(command.corner_round),
147    XtRImmediate,
148    (XtPointer)25
149  },
150};
151#undef offset
152
153static XtActionsRec actionsList[] = {
154  {"set",		Set},
155  {"notify",		Notify},
156  {"highlight",		Highlight},
157  {"reset",		Reset},
158  {"unset",		Unset},
159  {"unhighlight",	Unhighlight}
160};
161
162#define SuperClass ((LabelWidgetClass)&labelClassRec)
163
164CommandClassRec commandClassRec = {
165  /* core */
166  {
167    (WidgetClass)SuperClass,		/* superclass		  */
168    "Command",				/* class_name		  */
169    sizeof(CommandRec),			/* size			  */
170    XawCommandClassInitialize,		/* class_initialize	  */
171    NULL,				/* class_part_initialize  */
172    False,				/* class_inited		  */
173    XawCommandInitialize,		/* initialize		  */
174    NULL,				/* initialize_hook	  */
175    XawCommandRealize,			/* realize		  */
176    actionsList,			/* actions		  */
177    XtNumber(actionsList),		/* num_actions		  */
178    resources,				/* resources		  */
179    XtNumber(resources),		/* num_resources	  */
180    NULLQUARK,				/* xrm_class		  */
181    False,				/* compress_motion	  */
182    True,				/* compress_exposure	  */
183    True,				/* compress_enterleave	  */
184    False,				/* visible_interest	  */
185    XawCommandDestroy,			/* destroy		  */
186    XawCommandResize,			/* resize		  */
187    XawCommandRedisplay,		/* expose		  */
188    XawCommandSetValues,		/* set_values		  */
189    NULL,				/* set_values_hook	  */
190    XtInheritSetValuesAlmost,		/* set_values_almost	  */
191    XawCommandGetValuesHook,		/* get_values_hook	  */
192    NULL,				/* accept_focus		  */
193    XtVersion,				/* version		  */
194    NULL,				/* callback_private	  */
195    defaultTranslations,		/* tm_table		  */
196    XtInheritQueryGeometry,		/* query_geometry	  */
197    XtInheritDisplayAccelerator,	/* display_accelerator	  */
198    NULL,				/* extension */
199  },
200  /* simple */
201  {
202    ChangeSensitive,			/* change_sensitive */
203  },
204  /* label */
205  {
206    NULL,				/* not used */
207  },
208  /* command */
209  {
210    NULL,				/* not used */
211  },
212};
213
214WidgetClass commandWidgetClass = (WidgetClass)&commandClassRec;
215
216/*
217 * Implementation
218 */
219static GC
220Get_GC(CommandWidget cbw, Pixel fg, Pixel bg)
221{
222    XGCValues	values;
223
224    values.foreground	= fg;
225    values.background	= bg;
226    values.font		= cbw->label.font->fid;
227    values.cap_style	= CapProjecting;
228
229    if (cbw->command.highlight_thickness > 1)
230	values.line_width = cbw->command.highlight_thickness;
231    else
232	values.line_width = 0;
233
234    if (cbw->simple.international == True)
235	return (XtAllocateGC((Widget)cbw, 0,
236			     GCForeground | GCBackground | GCLineWidth |
237			     GCCapStyle, &values, GCFont, 0));
238    else
239	return (XtGetGC((Widget)cbw,
240			GCForeground | GCBackground | GCFont | GCLineWidth |
241			GCCapStyle, &values));
242}
243
244/*ARGSUSED*/
245static void
246XawCommandInitialize(Widget request, Widget cnew,
247		     ArgList args, Cardinal *num_args)
248{
249    CommandWidget cbw = (CommandWidget)cnew;
250    int shape_event_base, shape_error_base;
251
252    if (!cbw->label.font) XtError("Aborting: no font found\n");
253
254    if (cbw->command.shape_style != XawShapeRectangle &&
255	!XShapeQueryExtension(XtDisplay(cnew), &shape_event_base,
256			      &shape_error_base))
257	cbw->command.shape_style = XawShapeRectangle;
258
259    if (cbw->command.highlight_thickness == DEFAULT_SHAPE_HIGHLIGHT) {
260	if (cbw->command.shape_style != XawShapeRectangle)
261	    cbw->command.highlight_thickness = 0;
262	else
263	    cbw->command.highlight_thickness = DEFAULT_HIGHLIGHT_THICKNESS;
264    }
265
266    cbw->command.normal_GC = Get_GC(cbw, cbw->label.foreground,
267				    cbw->core.background_pixel);
268    cbw->command.inverse_GC = Get_GC(cbw, cbw->core.background_pixel,
269				     cbw->label.foreground);
270    XtReleaseGC(cnew, cbw->label.normal_GC);
271    cbw->label.normal_GC = cbw->command.normal_GC;
272
273    cbw->command.set = False;
274    cbw->command.highlighted = HighlightNone;
275}
276
277static Region
278HighlightRegion(CommandWidget cbw)
279{
280    static Region outerRegion = NULL, innerRegion, emptyRegion;
281    XRectangle rect;
282
283    if (cbw->command.highlight_thickness == 0 ||
284        cbw->command.highlight_thickness > Min(XtWidth(cbw), XtHeight(cbw)) / 2)
285	return (NULL);
286
287    if (outerRegion == NULL) {
288	/* save time by allocating scratch regions only once. */
289	outerRegion = XCreateRegion();
290	innerRegion = XCreateRegion();
291	emptyRegion = XCreateRegion();
292    }
293
294    rect.x = rect.y = 0;
295    rect.width = XtWidth(cbw);
296    rect.height = XtHeight(cbw);
297    XUnionRectWithRegion(&rect, emptyRegion, outerRegion);
298    rect.x = rect.y = cbw->command.highlight_thickness;
299    rect.width -= cbw->command.highlight_thickness * 2;
300    rect.height -= cbw->command.highlight_thickness * 2;
301    XUnionRectWithRegion(&rect, emptyRegion, innerRegion);
302    XSubtractRegion(outerRegion, innerRegion, outerRegion);
303
304    return (outerRegion);
305}
306
307/***************************
308*  Action Procedures
309***************************/
310static void
311XawCommandToggle(Widget w)
312{
313    CommandWidget xaw = (CommandWidget)w;
314    Arg args[2];
315    Cardinal num_args;
316
317    num_args = 0;
318    XtSetArg(args[num_args], XtNbackground,
319	     xaw->label.foreground);		++num_args;
320    XtSetArg(args[num_args], XtNforeground,
321	     xaw->core.background_pixel);	++num_args;
322    XtSetValues(w, args, num_args);
323}
324
325/*ARGSUSED*/
326static void
327Set(Widget w, XEvent *event, String *params, Cardinal *num_params)
328{
329    CommandWidget cbw = (CommandWidget)w;
330
331    if (cbw->command.set)
332	return;
333
334    XawCommandToggle(w);
335    cbw->command.set= True;
336}
337
338/*ARGSUSED*/
339static void
340Unset(Widget w, XEvent *event, String *params, Cardinal *num_params)
341{
342    CommandWidget cbw = (CommandWidget)w;
343
344    if (!cbw->command.set)
345	return;
346
347    cbw->command.set = False;
348    XawCommandToggle(w);
349}
350
351/*ARGSUSED*/
352static void
353Reset(Widget w, XEvent *event, String *params, Cardinal *num_params)
354{
355    CommandWidget cbw = (CommandWidget)w;
356
357    if (cbw->command.set) {
358	cbw->command.highlighted = HighlightNone;
359	Unset(w, event, params, num_params);
360    }
361    else
362	Unhighlight(w, event, params, num_params);
363}
364
365/*ARGSUSED*/
366static void
367Highlight(Widget w, XEvent *event, String *params, Cardinal *num_params)
368{
369    CommandWidget cbw = (CommandWidget)w;
370
371    if (*num_params == (Cardinal)0)
372	cbw->command.highlighted = HighlightWhenUnset;
373    else {
374	if (*num_params != (Cardinal)1)
375	    XtWarning("Too many parameters passed to highlight action table.");
376	switch (params[0][0]) {
377	    case 'A':
378	    case 'a':
379		cbw->command.highlighted = HighlightAlways;
380		break;
381	    default:
382		cbw->command.highlighted = HighlightWhenUnset;
383		break;
384	}
385    }
386
387    if (XtIsRealized(w))
388	PaintCommandWidget(w, event, HighlightRegion(cbw), True);
389}
390
391/*ARGSUSED*/
392static void
393Unhighlight(Widget w, XEvent *event, String *params, Cardinal *num_params)
394{
395    CommandWidget cbw = (CommandWidget)w;
396
397    cbw->command.highlighted = HighlightNone;
398    if (XtIsRealized(w))
399	PaintCommandWidget(w, event, HighlightRegion(cbw), True);
400}
401
402/*ARGSUSED*/
403static void
404Notify(Widget w, XEvent *event, String *params, Cardinal *num_params)
405{
406    CommandWidget cbw = (CommandWidget)w;
407
408    /* check to be sure state is still Set so that user can cancel
409       the action (e.g. by moving outside the window, in the default
410       bindings.
411    */
412    if (cbw->command.set)
413	XtCallCallbackList(w, cbw->command.callbacks, (XtPointer) NULL);
414}
415
416static void
417XawCommandRedisplay(Widget w, XEvent *event, Region region)
418{
419    PaintCommandWidget(w, event, region, False);
420}
421
422/*
423 * Function:
424 *	PaintCommandWidget
425 * Parameters:
426 *	w      - command widget
427 *	region - region to paint (passed to the superclass)
428 *                 change - did it change either set or highlight state?
429 */
430static void
431PaintCommandWidget(Widget w, XEvent *event, Region region, Bool change)
432{
433    CommandWidget cbw = (CommandWidget)w;
434    Bool very_thick;
435    GC rev_gc;
436
437    very_thick = cbw->command.highlight_thickness
438		 > Min(XtWidth(cbw), XtHeight(cbw)) / 2;
439
440    if (cbw->command.highlight_thickness == 0) {
441	(*SuperClass->core_class.expose) (w, event, region);
442	return;
443    }
444
445    /*
446     * If we are set then use the same colors as if we are not highlighted
447     */
448
449    if (cbw->command.highlighted != HighlightNone) {
450	rev_gc = cbw->command.normal_GC;
451    }
452    else {
453	rev_gc = cbw->command.inverse_GC;
454    }
455
456    if (!((!change && cbw->command.highlighted == HighlightNone)
457	|| (cbw->command.highlighted == HighlightWhenUnset
458	    && cbw->command.set))) {
459	if (very_thick)
460	    XFillRectangle(XtDisplay(w),XtWindow(w), rev_gc,
461			   0, 0, XtWidth(cbw), XtHeight(cbw));
462	else {
463	    /* wide lines are centered on the path, so indent it */
464	    if (cbw->core.background_pixmap != XtUnspecifiedPixmap &&
465		rev_gc == cbw->command.inverse_GC) {
466		XClearArea(XtDisplay(w), XtWindow(w),
467			   0, 0, XtWidth(cbw), cbw->command.highlight_thickness,
468			   False);
469		XClearArea(XtDisplay(w), XtWindow(w),
470			   0, cbw->command.highlight_thickness,
471			   cbw->command.highlight_thickness,
472			   XtHeight(cbw) - (cbw->command.highlight_thickness<<1),
473			   False);
474		XClearArea(XtDisplay(w), XtWindow(w),
475			   XtWidth(cbw) - cbw->command.highlight_thickness,
476			   cbw->command.highlight_thickness,
477			   cbw->command.highlight_thickness,
478			   XtHeight(cbw) - (cbw->command.highlight_thickness<<1),
479			   False);
480		XClearArea(XtDisplay(w), XtWindow(w),
481			   0, XtHeight(cbw) - cbw->command.highlight_thickness,
482			   XtWidth(cbw), cbw->command.highlight_thickness,
483			   False);
484	    }
485	    else {
486		int offset = cbw->command.highlight_thickness / 2;
487
488		XDrawRectangle(XtDisplay(w),XtWindow(w), rev_gc, offset, offset,
489			       XtWidth(cbw) - cbw->command.highlight_thickness,
490			      XtHeight(cbw) - cbw->command.highlight_thickness);
491	   }
492	}
493    }
494
495    (*SuperClass->core_class.expose)(w, event, region);
496}
497
498static void
499XawCommandDestroy(Widget w)
500{
501    CommandWidget cbw = (CommandWidget)w;
502
503    /* Label will release cbw->command.normal_GC */
504    XtReleaseGC(w, cbw->command.inverse_GC);
505}
506
507/*ARGSUSED*/
508static Boolean
509XawCommandSetValues(Widget current, Widget request, Widget cnew,
510		    ArgList args, Cardinal *num_args)
511{
512    CommandWidget oldcbw = (CommandWidget)current;
513    CommandWidget cbw = (CommandWidget)cnew;
514    Boolean redisplay = False;
515
516    if (oldcbw->core.sensitive != cbw->core.sensitive && !cbw->core.sensitive) {
517	cbw->command.highlighted = HighlightNone;
518	redisplay = True;
519    }
520
521    if (cbw->command.set) {
522	unsigned int i;
523	Pixel foreground, background;
524
525	foreground = oldcbw->label.foreground;
526	background = oldcbw->core.background_pixel;
527	for (i = 0; i < *num_args; i++) {
528	    if (STR_EQUAL(args[i].name, XtNforeground))
529		background = cbw->label.foreground;
530	    else if (STR_EQUAL(args[i].name, XtNbackground))
531		foreground = cbw->core.background_pixel;
532	}
533	cbw->label.foreground = foreground;
534	cbw->core.background_pixel = background;
535    }
536
537    if (oldcbw->label.foreground != cbw->label.foreground
538	|| oldcbw->core.background_pixel != cbw->core.background_pixel
539	|| oldcbw->command.highlight_thickness
540	!= cbw->command.highlight_thickness
541	|| oldcbw->label.font != cbw->label.font) {
542	XtReleaseGC(cnew, cbw->command.inverse_GC);
543
544	cbw->command.normal_GC = Get_GC(cbw, cbw->label.foreground,
545					cbw->core.background_pixel);
546	cbw->command.inverse_GC = Get_GC(cbw, cbw->core.background_pixel,
547					 cbw->label.foreground);
548	XtReleaseGC(cnew, cbw->label.normal_GC);
549	cbw->label.normal_GC = cbw->command.normal_GC;
550
551	redisplay = True;
552    }
553
554    if (XtIsRealized(cnew)
555	&& oldcbw->command.shape_style != cbw->command.shape_style
556	&& !ShapeButton(cbw, True))
557	cbw->command.shape_style = oldcbw->command.shape_style;
558
559    return (redisplay);
560}
561
562static void
563XawCommandGetValuesHook(Widget w, ArgList args, Cardinal *num_args)
564{
565    CommandWidget cbw = (CommandWidget)w;
566    unsigned int i;
567
568    for (i = 0; i < *num_args; i++) {
569	if (STR_EQUAL(args[i].name, XtNforeground))
570	    *((String*)args[i].value) = cbw->command.set ?
571		(String)cbw->core.background_pixel : (String)cbw->label.foreground;
572	else if (STR_EQUAL(args[i].name, XtNbackground))
573	    *((String*)args[i].value) = cbw->command.set ?
574		(String)cbw->label.foreground : (String)cbw->core.background_pixel;
575    }
576}
577
578static void
579XawCommandClassInitialize(void)
580{
581    XawInitializeWidgetSet();
582    XtSetTypeConverter(XtRString, XtRShapeStyle, XmuCvtStringToShapeStyle,
583		       NULL, 0, XtCacheNone, NULL);
584    XtSetTypeConverter(XtRShapeStyle, XtRString, XmuCvtShapeStyleToString,
585		       NULL, 0, XtCacheNone, NULL);
586}
587
588static Bool
589ShapeButton(CommandWidget cbw, Bool checkRectangular)
590{
591    Dimension corner_size = 0;
592
593    if (cbw->command.shape_style == XawShapeRoundedRectangle) {
594	corner_size = XtWidth(cbw) < XtHeight(cbw) ?
595			XtWidth(cbw) : XtHeight(cbw);
596	corner_size = (corner_size * cbw->command.corner_round) / 100;
597    }
598
599    if (checkRectangular || cbw->command.shape_style != XawShapeRectangle) {
600	if (!XmuReshapeWidget((Widget)cbw, cbw->command.shape_style,
601			      corner_size, corner_size)) {
602	    cbw->command.shape_style = XawShapeRectangle;
603	    return (False);
604	}
605    }
606
607    return (True);
608}
609
610static void
611XawCommandRealize(Widget w, Mask *valueMask, XSetWindowAttributes *attributes)
612{
613    (*commandWidgetClass->core_class.superclass->core_class.realize)
614	(w, valueMask, attributes);
615
616    ShapeButton((CommandWidget)w, False);
617}
618
619static void
620XawCommandResize(Widget w)
621{
622    if (XtIsRealized(w))
623	ShapeButton((CommandWidget)w, False);
624
625    (*commandWidgetClass->core_class.superclass->core_class.resize)(w);
626}
627
628static Bool
629ChangeSensitive(Widget w)
630{
631    CommandWidget cbw = (CommandWidget)w;
632
633    if (XtIsRealized(w)) {
634	if (XtIsSensitive(w)) {
635	    if (w->core.border_pixmap != XtUnspecifiedPixmap)
636		XSetWindowBorderPixmap(XtDisplay(w), XtWindow(w),
637				       w->core.border_pixmap);
638	    else
639		XSetWindowBorder(XtDisplay(w), XtWindow(w),
640				 w->core.border_pixel);
641	}
642	else {
643	    if (cbw->simple.insensitive_border == None)
644		cbw->simple.insensitive_border =
645		    XmuCreateStippledPixmap(XtScreen(w),
646					    w->core.border_pixel,
647					    cbw->command.set ?
648						cbw->label.foreground :
649						w->core.background_pixel,
650					    w->core.depth);
651	    XSetWindowBorderPixmap(XtDisplay(w), XtWindow(w),
652				   cbw->simple.insensitive_border);
653	}
654    }
655
656    return (False);
657}
658