/*
* tkMacOSXWindowEvent.c --
*
* This file defines the routines for both creating and handling
* Window Manager class events for Tk.
*
* Copyright 2001, Apple Computer, Inc.
* Copyright (c) 2005 Daniel A. Steffen <[email protected]>
*
* See the file "license.terms" for information on usage and redistribution of
* this file, and for a DISCLAIMER OF ALL WARRANTIES.
*
* The following terms apply to all files originating from Apple
* Computer, Inc. ("Apple") and associated with the software
* unless explicitly disclaimed in individual files.
*
*
* Apple hereby grants permission to use, copy, modify,
* distribute, and license this software and its documentation
* for any purpose, provided that existing copyright notices are
* retained in all copies and that this notice is included
* verbatim in any distributions. No written agreement, license,
* or royalty fee is required for any of the authorized
* uses. Modifications to this software may be copyrighted by
* their authors and need not follow the licensing terms
* described here, provided that the new terms are clearly
* indicated on the first page of each file where they apply.
*
*
* IN NO EVENT SHALL APPLE, THE AUTHORS OR DISTRIBUTORS OF THE
* SOFTWARE BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL,
* INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF
* THIS SOFTWARE, ITS DOCUMENTATION, OR ANY DERIVATIVES THEREOF,
* EVEN IF APPLE OR THE AUTHORS HAVE BEEN ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE. APPLE, THE AUTHORS AND
* DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES, INCLUDING,
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. THIS
* SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, AND APPLE,THE
* AUTHORS AND DISTRIBUTORS HAVE NO OBLIGATION TO PROVIDE
* MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
*
* GOVERNMENT USE: If you are acquiring this software on behalf
* of the U.S. government, the Government shall have only
* "Restricted Rights" in the software and related documentation
* as defined in the Federal Acquisition Regulations (FARs) in
* Clause 52.227.19 (c) (2). If you are acquiring the software
* on behalf of the Department of Defense, the software shall be
* classified as "Commercial Computer Software" and the
* Government shall have only "Restricted Rights" as defined in
* Clause 252.227-7013 (c) (1) of DFARs. Notwithstanding the
* foregoing, the authors grant the U.S. Government and others
* acting in its behalf permission to use and distribute the
* software in accordance with the terms specified in this
* license.
*
* RCS: @(#) $Id: tkMacOSXWindowEvent.c,v 1.3.2.15 2006/09/10 17:07:36 das Exp $
*/
#include "tkMacOSXInt.h"
#include "tkMacOSXWm.h"
#include "tkMacOSXEvent.h"
#include "tkMacOSXDebug.h"
/*
* Declarations of global variables defined in this file.
*/
static int tkMacOSXAppInFront = true; /* Boolean variable for determining if
* we are the frontmost app. Only set
* in TkMacOSXProcessApplicationEvent
*/
static RgnHandle gDamageRgn;
static RgnHandle visRgn;
/*
* Declaration of functions used only in this file
*/
static int GenerateUpdateEvent( Window window);
static void GenerateUpdates( RgnHandle updateRgn, TkWindow *winPtr);
static int GenerateActivateEvents( Window window, int activeFlag);
/*
*----------------------------------------------------------------------
*
* TkMacOSXProcessApplicationEvent --
*
* This processes Application level events, mainly activate
* and deactivate.
*
* Results:
* o.
*
* Side effects:
* Hide or reveal floating windows, and set tkMacOSXAppInFront.
*
*----------------------------------------------------------------------
*/
MODULE_SCOPE int
TkMacOSXProcessApplicationEvent(
TkMacOSXEvent *eventPtr,
MacEventStatus *statusPtr)
{
Tcl_CmdInfo dummy;
/*
* This is a bit of a hack. We get "show" events both when we come back
* from being hidden, and whenever we are activated. I only want to run
* the "show" proc when we have been hidden already, not as a substitute
* for <Activate>. So I use this toggle...
*/
static int toggleHide = 0;
switch (eventPtr->eKind) {
case kEventAppActivated:
tkMacOSXAppInFront = true;
ShowFloatingWindows();
break;
case kEventAppDeactivated:
TkSuspendClipboard();
tkMacOSXAppInFront = false;
HideFloatingWindows();
break;
case kEventAppQuit:
statusPtr->stopProcessing = 1;
break;
case kEventAppHidden:
if (toggleHide == 0) {
toggleHide = 1;
if (eventPtr->interp && Tcl_GetCommandInfo(eventPtr->interp,
"::tk::mac::OnHide", &dummy)) {
Tcl_GlobalEval(eventPtr->interp, "::tk::mac::OnHide");
}
}
statusPtr->stopProcessing = 1;
break;
case kEventAppShown:
if (toggleHide == 1) {
toggleHide = 0;
if (eventPtr->interp && Tcl_GetCommandInfo(eventPtr->interp,
"::tk::mac::OnShow", &dummy)) {
Tcl_GlobalEval(eventPtr->interp, "::tk::mac::OnShow");
}
}
statusPtr->stopProcessing = 1;
break;
case kEventAppAvailableWindowBoundsChanged: {
TkDisplay *dispPtr = TkGetDisplayList();
TkMacOSXDisplayChanged(dispPtr->display);
/*
* Should we call ::tk::mac::OnDisplayChanged?
*/
break;
}
default:
break;
}
return 0;
}
/*
*----------------------------------------------------------------------
*
* TkMacOSXProcessWindowEvent --
*
* This processes Window level events, mainly activate
* and deactivate.
*
* Results:
* 0.
*
* Side effects:
* Cause Windows to be moved forward or backward in the
* window stack.
*
*----------------------------------------------------------------------
*/
MODULE_SCOPE int
TkMacOSXProcessWindowEvent(
TkMacOSXEvent * eventPtr,
MacEventStatus * statusPtr)
{
OSStatus status;
WindowRef whichWindow;
Window window;
int eventFound = false;
switch (eventPtr->eKind) {
case kEventWindowActivated:
case kEventWindowDeactivated:
case kEventWindowUpdate:
case kEventWindowExpanded:
break;
default:
return 0;
break;
}
status = GetEventParameter(eventPtr->eventRef,
kEventParamDirectObject,
typeWindowRef, NULL,
sizeof(whichWindow), NULL,
&whichWindow);
if (status != noErr) {
#ifdef TK_MAC_DEBUG
fprintf ( stderr, "TkMacOSXHandleWindowEvent:Failed to retrieve window" );
#endif
return 0;
}
window = TkMacOSXGetXWindow(whichWindow);
switch (eventPtr->eKind) {
case kEventWindowActivated:
eventFound |= GenerateActivateEvents(window, 1);
eventFound |= TkMacOSXGenerateFocusEvent(window, 1);
break;
case kEventWindowDeactivated:
eventFound |= GenerateActivateEvents(window, 0);
eventFound |= TkMacOSXGenerateFocusEvent(window, 0);
break;
case kEventWindowUpdate:
if (GenerateUpdateEvent(window)) {
eventFound = true;
}
break;
case kEventWindowExpanded: {
TkDisplay *dispPtr;
TkWindow *winPtr;
dispPtr = TkGetDisplayList();
winPtr = (TkWindow *)Tk_IdToWindow(dispPtr->display, window);
if (winPtr) {
TkpWmSetState(winPtr, TkMacOSXIsWindowZoomed(winPtr) ?
ZoomState : NormalState);
}
break;
}
}
return 0;
}
/*
*----------------------------------------------------------------------
*
* GenerateUpdateEvent --
*
* Given a Macintosh window update event this function generates all the
* X update events needed by Tk.
*
* Results:
* True if event(s) are generated - false otherwise.
*
* Side effects:
* Additional events may be place on the Tk event queue.
*
*----------------------------------------------------------------------
*/
static int
GenerateUpdateEvent(Window window)
{
WindowRef macWindow;
TkDisplay * dispPtr;
TkWindow * winPtr;
Rect bounds;
dispPtr = TkGetDisplayList();
winPtr = (TkWindow *)Tk_IdToWindow(dispPtr->display, window);
if (winPtr ==NULL ){
return false;
}
if (gDamageRgn == NULL) {
gDamageRgn = NewRgn();
}
if (visRgn == NULL) {
visRgn = NewRgn();
}
macWindow = GetWindowFromPort(TkMacOSXGetDrawablePort(window));
BeginUpdate(macWindow);
/*
* In the Classic version of the code, this was the "visRgn" field of the WindowRec
* This no longer exists in OS X, so retrieve the content region instead
* Note that this is in screen coordinates
* We therefore convert it to window relative coordinates
*/
GetWindowRegion (macWindow, kWindowContentRgn, visRgn );
GetRegionBounds(visRgn,&bounds);
bounds.right -= bounds.left;
bounds.bottom -= bounds.top;
bounds.left = 0;
bounds.top=0;
RectRgn(visRgn, &bounds);
GenerateUpdates(visRgn, winPtr);
EndUpdate(macWindow);
return true;
}
/*
*----------------------------------------------------------------------
*
* GenerateUpdates --
*
* Given a Macintosh update region and a Tk window this function
* geneates a X damage event for the window if it is within the
* update region. The function will then recursivly have each
* damaged window generate damage events for its child windows.
*
* Results:
* None.
*
* Side effects:
* Additional events may be place on the Tk event queue.
*
*----------------------------------------------------------------------
*/
static void
GenerateUpdates(
RgnHandle updateRgn,
TkWindow *winPtr)
{
TkWindow *childPtr;
XEvent event;
Rect bounds, updateBounds, damageBounds;
TkMacOSXWinBounds(winPtr, &bounds);
GetRegionBounds(updateRgn,&updateBounds);
if (bounds.top > updateBounds.bottom ||
updateBounds.top > bounds.bottom ||
bounds.left > updateBounds.right ||
updateBounds.left > bounds.right ||
!RectInRgn(&bounds, updateRgn)) {
return;
}
if (!RectInRgn(&bounds, updateRgn)) {
return;
}
event.xany.serial = Tk_Display(winPtr)->request;
event.xany.send_event = false;
event.xany.window = Tk_WindowId(winPtr);
event.xany.display = Tk_Display(winPtr);
event.type = Expose;
/*
* Compute the bounding box of the area that the damage occured in.
*/
/*
* CopyRgn(TkMacOSXVisableClipRgn(winPtr), rgn);
* TODO: this call doesn't work doing resizes!!!
*/
RectRgn(gDamageRgn, &bounds);
SectRgn(gDamageRgn, updateRgn, gDamageRgn);
OffsetRgn(gDamageRgn, -bounds.left, -bounds.top);
GetRegionBounds(gDamageRgn,&damageBounds);
event.xexpose.x = damageBounds.left;
event.xexpose.y = damageBounds.top;
event.xexpose.width = damageBounds.right-damageBounds.left;
event.xexpose.height = damageBounds.bottom-damageBounds.top;
event.xexpose.count = 0;
Tk_QueueWindowEvent(&event, TCL_QUEUE_TAIL);
/*
* Generate updates for the children of this window
*/
for (childPtr = winPtr->childList; childPtr != NULL;
childPtr = childPtr->nextPtr) {
if (!Tk_IsMapped(childPtr) || Tk_IsTopLevel(childPtr)) {
continue;
}
GenerateUpdates(updateRgn, childPtr);
}
/*
* Generate updates for any contained windows
*/
if (Tk_IsContainer(winPtr)) {
childPtr = TkpGetOtherWindow(winPtr);
if (childPtr != NULL && Tk_IsMapped(childPtr)) {
GenerateUpdates(updateRgn, childPtr);
}
/*
* NOTE: Here we should handle out of process embedding.
*/
}
return;
}
/*
*----------------------------------------------------------------------
*
* GenerateActivateEvents --
*
* Given a Macintosh window activate event this function generates all the
* X Activate events needed by Tk.
*
* Results:
* True if event(s) are generated - false otherwise.
*
* Side effects:
* Additional events may be place on the Tk event queue.
*
*----------------------------------------------------------------------
*/
int
GenerateActivateEvents(
Window window, /* Root X window for event. */
int activeFlag )
{
TkWindow *winPtr;
TkDisplay *dispPtr;
dispPtr = TkGetDisplayList();
winPtr = (TkWindow *) Tk_IdToWindow(dispPtr->display, window);
if (winPtr == NULL || winPtr->window == None) {
return false;
}
TkGenerateActivateEvents(winPtr,activeFlag);
return true;
}
/*
*----------------------------------------------------------------------
*
* TkMacOSXGenerateFocusEvent --
*
* Given a Macintosh window activate event this function generates all the
* X Focus events needed by Tk.
*
* Results:
* True if event(s) are generated - false otherwise.
*
* Side effects:
* Additional events may be place on the Tk event queue.
*
*----------------------------------------------------------------------
*/
MODULE_SCOPE int
TkMacOSXGenerateFocusEvent(
Window window, /* Root X window for event. */
int activeFlag )
{
XEvent event;
Tk_Window tkwin;
TkDisplay *dispPtr;
dispPtr = TkGetDisplayList();
tkwin = Tk_IdToWindow(dispPtr->display, window);
if (tkwin == NULL) {
return false;
}
/*
* Don't send focus events to windows of class help or to
* windows with the kWindowNoActivatesAttribute.
*/
if (((TkWindow *)tkwin)->wmInfoPtr->macClass == kHelpWindowClass ||
((TkWindow *)tkwin)->wmInfoPtr->attributes &
kWindowNoActivatesAttribute) {
return false;
}
/*
* Generate FocusIn and FocusOut events. This event
* is only sent to the toplevel window.
*/
if (activeFlag) {
event.xany.type = FocusIn;
} else {
event.xany.type = FocusOut;
}
event.xany.serial = dispPtr->display->request;
event.xany.send_event = False;
event.xfocus.display = dispPtr->display;
event.xfocus.window = window;
event.xfocus.mode = NotifyNormal;
event.xfocus.detail = NotifyDetailNone;
Tk_QueueWindowEvent(&event, TCL_QUEUE_TAIL);
return true;
}
/*
*----------------------------------------------------------------------
*
* TkGenWMConfigureEvent --
*
* Generate a ConfigureNotify event for Tk. Depending on the
* value of flag the values of width/height, x/y, or both may
* be changed.
*
* Results:
* None.
*
* Side effects:
* A ConfigureNotify event is sent to Tk.
*
*----------------------------------------------------------------------
*/
void
TkGenWMConfigureEvent(
Tk_Window tkwin,
int x,
int y,
int width,
int height,
int flags)
{
XEvent event;
WmInfo *wmPtr;
TkWindow *winPtr = (TkWindow *) tkwin;
if (tkwin == NULL) {
return;
}
event.type = ConfigureNotify;
event.xconfigure.serial = Tk_Display(tkwin)->request;
event.xconfigure.send_event = False;
event.xconfigure.display = Tk_Display(tkwin);
event.xconfigure.event = Tk_WindowId(tkwin);
event.xconfigure.window = Tk_WindowId(tkwin);
event.xconfigure.border_width = winPtr->changes.border_width;
event.xconfigure.override_redirect = winPtr->atts.override_redirect;
if (winPtr->changes.stack_mode == Above) {
event.xconfigure.above = winPtr->changes.sibling;
} else {
event.xconfigure.above = None;
}
if (flags & TK_LOCATION_CHANGED) {
event.xconfigure.x = x;
event.xconfigure.y = y;
} else {
event.xconfigure.x = Tk_X(tkwin);
event.xconfigure.y = Tk_Y(tkwin);
x = Tk_X(tkwin);
y = Tk_Y(tkwin);
}
if (flags & TK_SIZE_CHANGED) {
event.xconfigure.width = width;
event.xconfigure.height = height;
} else {
event.xconfigure.width = Tk_Width(tkwin);
event.xconfigure.height = Tk_Height(tkwin);
width = Tk_Width(tkwin);
height = Tk_Height(tkwin);
}
Tk_QueueWindowEvent(&event, TCL_QUEUE_TAIL);
/*
* Update window manager information.
*/
if (Tk_IsTopLevel(winPtr)) {
wmPtr = winPtr->wmInfoPtr;
if (flags & TK_LOCATION_CHANGED) {
wmPtr->x = x;
wmPtr->y = y;
wmPtr->flags &= ~(WM_NEGATIVE_X | WM_NEGATIVE_Y);
}
if ((flags & TK_SIZE_CHANGED) &&
((width != Tk_Width(tkwin)) || (height != Tk_Height(tkwin)))) {
if ((wmPtr->width == -1) && (width == winPtr->reqWidth)) {
/*
* Don't set external width, since the user didn't change it
* from what the widgets asked for.
*/
} else {
if (wmPtr->gridWin != NULL) {
wmPtr->width = wmPtr->reqGridWidth
+ (width - winPtr->reqWidth)/wmPtr->widthInc;
if (wmPtr->width < 0) {
wmPtr->width = 0;
}
} else {
wmPtr->width = width;
}
}
if ((wmPtr->height == -1) && (height == winPtr->reqHeight)) {
/*
* Don't set external height, since the user didn't change it
* from what the widgets asked for.
*/
} else {
if (wmPtr->gridWin != NULL) {
wmPtr->height = wmPtr->reqGridHeight
+ (height - winPtr->reqHeight)/wmPtr->heightInc;
if (wmPtr->height < 0) {
wmPtr->height = 0;
}
} else {
wmPtr->height = height;
}
}
wmPtr->configWidth = width;
wmPtr->configHeight = height;
}
}
/*
* Now set up the changes structure. Under X we wait for the
* ConfigureNotify to set these values. On the Mac we know imediatly that
* this is what we want - so we just set them. However, we need to
* make sure the windows clipping region is marked invalid so the
* change is visible to the subwindow.
*/
winPtr->changes.x = x;
winPtr->changes.y = y;
winPtr->changes.width = width;
winPtr->changes.height = height;
TkMacOSXInvalClipRgns(tkwin);
}
/*
*----------------------------------------------------------------------
*
* TkGenWMDestroyEvent --
*
* Generate a WM Destroy event for Tk. *
* Results:
* None.
*
* Side effects:
* A WM_PROTOCOL/WM_DELETE_WINDOW event is sent to Tk.
*
*----------------------------------------------------------------------
*/
void
TkGenWMDestroyEvent(
Tk_Window tkwin)
{
XEvent event;
event.xany.serial = Tk_Display(tkwin)->request;
event.xany.send_event = False;
event.xany.display = Tk_Display(tkwin);
event.xclient.window = Tk_WindowId(tkwin);
event.xclient.type = ClientMessage;
event.xclient.message_type = Tk_InternAtom(tkwin, "WM_PROTOCOLS");
event.xclient.format = 32;
event.xclient.data.l[0] = Tk_InternAtom(tkwin, "WM_DELETE_WINDOW");
Tk_QueueWindowEvent(&event, TCL_QUEUE_TAIL);
}
/*
*----------------------------------------------------------------------
*
* TkWmProtocolEventProc --
*
* This procedure is called by the Tk_HandleEvent whenever a
* ClientMessage event arrives whose type is "WM_PROTOCOLS".
* This procedure handles the message from the window manager
* in an appropriate fashion.
*
* Results:
* None.
*
* Side effects:
* Depends on what sort of handler, if any, was set up for the
* protocol.
*
*----------------------------------------------------------------------
*/
void
TkWmProtocolEventProc(
TkWindow *winPtr, /* Window to which the event was sent. */
XEvent *eventPtr) /* X event. */
{
WmInfo *wmPtr;
ProtocolHandler *protPtr;
Tcl_Interp *interp;
Atom protocol;
int result;
wmPtr = winPtr->wmInfoPtr;
if (wmPtr == NULL) {
return;
}
protocol = (Atom) eventPtr->xclient.data.l[0];
for (protPtr = wmPtr->protPtr; protPtr != NULL;
protPtr = protPtr->nextPtr) {
if (protocol == protPtr->protocol) {
Tcl_Preserve((ClientData) protPtr);
interp = protPtr->interp;
Tcl_Preserve((ClientData) interp);
result = Tcl_GlobalEval(interp, protPtr->command);
if (result != TCL_OK) {
Tcl_AddErrorInfo(interp, "\n (command for \"");
Tcl_AddErrorInfo(interp,
Tk_GetAtomName((Tk_Window) winPtr, protocol));
Tcl_AddErrorInfo(interp, "\" window manager protocol)");
Tk_BackgroundError(interp);
}
Tcl_Release((ClientData) interp);
Tcl_Release((ClientData) protPtr);
return;
}
}
/*
* No handler was present for this protocol. If this is a
* WM_DELETE_WINDOW message then just destroy the window.
*/
if (protocol == Tk_InternAtom((Tk_Window) winPtr, "WM_DELETE_WINDOW")) {
Tk_DestroyWindow((Tk_Window) winPtr);
}
}
/*
*----------------------------------------------------------------------
*
* Tk_MacOSXIsAppInFront --
*
* Returns 1 if this app is the foreground app.
*
* Results:
* 1 if app is in front, 0 otherwise.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
int
Tk_MacOSXIsAppInFront (void)
{
return tkMacOSXAppInFront;
}