1/*
2 * tkMacOSXWindowEvent.c --
3 *
4 *	This file defines the routines for both creating and handling Window
5 *	Manager class events for Tk.
6 *
7 * Copyright 2001-2009, Apple Inc.
8 * Copyright (c) 2005-2009 Daniel A. Steffen <das@users.sourceforge.net>
9 *
10 * See the file "license.terms" for information on usage and redistribution of
11 * this file, and for a DISCLAIMER OF ALL WARRANTIES.
12 *
13 * RCS: @(#) $Id$
14 */
15
16#include "tkMacOSXPrivate.h"
17#include "tkMacOSXWm.h"
18#include "tkMacOSXEvent.h"
19#include "tkMacOSXDebug.h"
20
21/*
22#ifdef TK_MAC_DEBUG
23#define TK_MAC_DEBUG_EVENTS
24#define TK_MAC_DEBUG_DRAWING
25#endif
26*/
27
28/*
29 * Declaration of functions used only in this file
30 */
31
32static int		GenerateUpdates(HIMutableShapeRef updateRgn,
33			    CGRect *updateBounds, TkWindow *winPtr);
34static int		GenerateActivateEvents(TkWindow *winPtr,
35			    int activeFlag);
36static void		DoWindowActivate(ClientData clientData);
37
38#pragma mark TKApplication(TKWindowEvent)
39
40#ifdef TK_MAC_DEBUG_NOTIFICATIONS
41extern NSString *NSWindowWillOrderOnScreenNotification;
42extern NSString *NSWindowDidOrderOnScreenNotification;
43extern NSString *NSWindowDidOrderOffScreenNotification;
44
45#if MAC_OS_X_VERSION_MIN_REQUIRED < 1060
46#define NSWindowWillStartLiveResizeNotification @"NSWindowWillStartLiveResizeNotification"
47#define NSWindowDidEndLiveResizeNotification  @"NSWindowDidEndLiveResizeNotification"
48#endif
49#endif
50
51@implementation TKApplication(TKWindowEvent)
52- (void)windowActivation:(NSNotification *)notification {
53#ifdef TK_MAC_DEBUG_NOTIFICATIONS
54    TKLog(@"-[%@(%p) %s] %@", [self class], self, _cmd, notification);
55#endif
56    BOOL activate = [[notification name] isEqualToString:NSWindowDidBecomeKeyNotification];
57    NSWindow *w = [notification object];
58    TkWindow *winPtr = TkMacOSXGetTkWindow(w);
59
60    if (winPtr && Tk_IsMapped(winPtr)) {
61	GenerateActivateEvents(winPtr, activate);
62    }
63}
64- (void)windowBoundsChanged:(NSNotification *)notification {
65#ifdef TK_MAC_DEBUG_NOTIFICATIONS
66    TKLog(@"-[%@(%p) %s] %@", [self class], self, _cmd, notification);
67#endif
68    BOOL movedOnly = [[notification name] isEqualToString:NSWindowDidMoveNotification];
69    if (movedOnly) {
70	/* constraining to screen after move not needed with AppKit */
71    }
72    NSWindow *w = [notification object];
73    TkWindow *winPtr = TkMacOSXGetTkWindow(w);
74
75    if (winPtr) {
76	WmInfo *wmPtr = winPtr->wmInfoPtr;
77	NSRect bounds = [w frame];
78	int x, y, width = -1, height = -1, flags = 0;
79
80	x = bounds.origin.x;
81	y = tkMacOSXZeroScreenHeight - (bounds.origin.y + bounds.size.height);
82	if (winPtr->changes.x != x || winPtr->changes.y != y){
83	    flags |= TK_LOCATION_CHANGED;
84	} else {
85	    x = y = -1;
86	}
87	if (!movedOnly && (winPtr->changes.width != bounds.size.width ||
88		winPtr->changes.height !=  bounds.size.height)) {
89	    width = bounds.size.width - wmPtr->xInParent;
90	    height = bounds.size.height - wmPtr->yInParent;
91	    flags |= TK_SIZE_CHANGED;
92	}
93	if (Tcl_GetServiceMode() != TCL_SERVICE_NONE) {
94	    /* propagate geometry changes immediately */
95	    flags |= TK_MACOSX_HANDLE_EVENT_IMMEDIATELY;
96	}
97	TkGenWMConfigureEvent((Tk_Window) winPtr, x, y, width, height, flags);
98    }
99}
100- (void)windowExpanded:(NSNotification *)notification {
101#ifdef TK_MAC_DEBUG_NOTIFICATIONS
102    TKLog(@"-[%@(%p) %s] %@", [self class], self, _cmd, notification);
103#endif
104    NSWindow *w = [notification object];
105    TkWindow *winPtr = TkMacOSXGetTkWindow(w);
106
107    if (winPtr) {
108	winPtr->wmInfoPtr->hints.initial_state =
109		TkMacOSXIsWindowZoomed(winPtr) ? ZoomState : NormalState;
110	Tk_MapWindow((Tk_Window) winPtr);
111	if (Tcl_GetServiceMode() != TCL_SERVICE_NONE) {
112	    /* Process all Tk events generated by Tk_MapWindow() */
113	    while (Tcl_ServiceEvent(0)) {}
114	    while (Tcl_DoOneEvent(TCL_IDLE_EVENTS|TCL_DONT_WAIT)) {}
115
116	    /*
117	     * NSWindowDidDeminiaturizeNotification is received after
118	     * NSWindowDidBecomeKeyNotification, so activate manually
119	     */
120
121	    GenerateActivateEvents(winPtr, 1);
122	} else {
123	    Tcl_DoWhenIdle(DoWindowActivate, winPtr);
124	}
125    }
126}
127- (void)windowCollapsed:(NSNotification *)notification {
128#ifdef TK_MAC_DEBUG_NOTIFICATIONS
129    TKLog(@"-[%@(%p) %s] %@", [self class], self, _cmd, notification);
130#endif
131    NSWindow *w = [notification object];
132    TkWindow *winPtr = TkMacOSXGetTkWindow(w);
133
134    if (winPtr) {
135	Tk_UnmapWindow((Tk_Window) winPtr);
136    }
137}
138- (BOOL)windowShouldClose:(NSWindow *)w {
139#ifdef TK_MAC_DEBUG_NOTIFICATIONS
140    TKLog(@"-[%@(%p) %s] %@", [self class], self, _cmd, w);
141#endif
142    TkWindow *winPtr = TkMacOSXGetTkWindow(w);
143
144    if (winPtr) {
145	TkGenWMDestroyEvent((Tk_Window) winPtr);
146    }
147
148    /*
149     * If necessary, TkGenWMDestroyEvent() handles [close]ing the window,
150     * so can always return NO from -windowShouldClose: for a Tk window.
151     */
152
153    return (winPtr ? NO : YES);
154}
155#ifdef TK_MAC_DEBUG_NOTIFICATIONS
156- (void)windowDragStart:(NSNotification *)notification {
157    TKLog(@"-[%@(%p) %s] %@", [self class], self, _cmd, notification);
158}
159- (void)windowLiveResize:(NSNotification *)notification {
160    TKLog(@"-[%@(%p) %s] %@", [self class], self, _cmd, notification);
161    //BOOL start = [[notification name] isEqualToString:NSWindowWillStartLiveResizeNotification];
162}
163- (void)windowMapped:(NSNotification *)notification {
164    TKLog(@"-[%@(%p) %s] %@", [self class], self, _cmd, notification);
165    NSWindow *w = [notification object];
166    TkWindow *winPtr = TkMacOSXGetTkWindow(w);
167
168    if (winPtr) {
169	//Tk_MapWindow((Tk_Window) winPtr);
170    }
171}
172- (void)windowBecameVisible:(NSNotification *)notification {
173#ifdef TK_MAC_DEBUG_NOTIFICATIONS
174    TKLog(@"-[%@(%p) %s] %@", [self class], self, _cmd, notification);
175#endif
176}
177- (void)windowUnmapped:(NSNotification *)notification {
178    TKLog(@"-[%@(%p) %s] %@", [self class], self, _cmd, notification);
179    NSWindow *w = [notification object];
180    TkWindow *winPtr = TkMacOSXGetTkWindow(w);
181
182    if (winPtr) {
183	//Tk_UnmapWindow((Tk_Window) winPtr);
184    }
185}
186#endif
187
188- (void)_setupWindowNotifications {
189    NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
190#define observe(n, s) [nc addObserver:self selector:@selector(s) name:(n) object:nil]
191    observe(NSWindowDidBecomeKeyNotification, windowActivation:);
192    observe(NSWindowDidResignKeyNotification, windowActivation:);
193    observe(NSWindowDidMoveNotification, windowBoundsChanged:);
194    observe(NSWindowDidResizeNotification, windowBoundsChanged:);
195    observe(NSWindowDidDeminiaturizeNotification, windowExpanded:);
196    observe(NSWindowDidMiniaturizeNotification, windowCollapsed:);
197#ifdef TK_MAC_DEBUG_NOTIFICATIONS
198    observe(NSWindowWillMoveNotification, windowDragStart:);
199    observe(NSWindowWillStartLiveResizeNotification, windowLiveResize:);
200    observe(NSWindowDidEndLiveResizeNotification, windowLiveResize:);
201    observe(NSWindowWillOrderOnScreenNotification, windowMapped:);
202    observe(NSWindowDidOrderOnScreenNotification, windowBecameVisible:);
203    observe(NSWindowDidOrderOffScreenNotification, windowUnmapped:);
204#endif
205#undef observe
206}
207@end
208
209#pragma mark TKApplication(TKApplicationEvent)
210
211@implementation TKApplication(TKApplicationEvent)
212- (void)applicationActivate:(NSNotification *)notification {
213#ifdef TK_MAC_DEBUG_NOTIFICATIONS
214    TKLog(@"-[%@(%p) %s] %@", [self class], self, _cmd, notification);
215#endif
216    [NSApp tkCheckPasteboard];
217}
218- (void)applicationDeactivate:(NSNotification *)notification {
219#ifdef TK_MAC_DEBUG_NOTIFICATIONS
220    TKLog(@"-[%@(%p) %s] %@", [self class], self, _cmd, notification);
221#endif
222    TkSuspendClipboard();
223}
224- (void)applicationShowHide:(NSNotification *)notification {
225#ifdef TK_MAC_DEBUG_NOTIFICATIONS
226    TKLog(@"-[%@(%p) %s] %@", [self class], self, _cmd, notification);
227#endif
228    const char *cmd = ([[notification name] isEqualToString:
229	    NSApplicationDidUnhideNotification] ?
230	    "::tk::mac::OnShow" : "::tk::mac::OnHide");
231    Tcl_CmdInfo dummy;
232
233    if (_eventInterp && Tcl_GetCommandInfo(_eventInterp, cmd, &dummy)) {
234	int code = Tcl_EvalEx(_eventInterp, cmd, -1, TCL_EVAL_GLOBAL);
235	if (code != TCL_OK) {
236	    Tcl_BackgroundError(_eventInterp);
237	}
238	Tcl_ResetResult(_eventInterp);
239    }
240}
241- (void)displayChanged:(NSNotification *)notification {
242#ifdef TK_MAC_DEBUG_NOTIFICATIONS
243    TKLog(@"-[%@(%p) %s] %@", [self class], self, _cmd, notification);
244#endif
245    TkDisplay *dispPtr = TkGetDisplayList();
246
247    if (dispPtr) {
248	TkMacOSXDisplayChanged(dispPtr->display);
249    }
250}
251@end
252
253#pragma mark -
254
255/*
256 *----------------------------------------------------------------------
257 *
258 * GenerateUpdates --
259 *
260 *	Given a Macintosh update region and a Tk window this function geneates
261 *	a X Expose event for the window if it is within the update region. The
262 *	function will then recursivly have each damaged window generate Expose
263 *	events for its child windows.
264 *
265 * Results:
266 *	True if event(s) are generated - false otherwise.
267 *
268 * Side effects:
269 *	Additional events may be place on the Tk event queue.
270 *
271 *----------------------------------------------------------------------
272 */
273
274static int
275GenerateUpdates(
276    HIMutableShapeRef updateRgn,
277    CGRect *updateBounds,
278    TkWindow *winPtr)
279{
280    TkWindow *childPtr;
281    XEvent event;
282    CGRect bounds, damageBounds;
283    HIShapeRef boundsRgn, damageRgn;
284
285    TkMacOSXWinCGBounds(winPtr, &bounds);
286    if (!CGRectIntersectsRect(bounds, *updateBounds)) {
287	return 0;
288    }
289    if (!HIShapeIntersectsRect(updateRgn, &bounds)) {
290	return 0;
291    }
292
293    /*
294     * Compute the bounding box of the area that the damage occured in.
295     */
296
297    boundsRgn = HIShapeCreateWithRect(&bounds);
298    damageRgn = HIShapeCreateIntersection(updateRgn, boundsRgn);
299    if (HIShapeIsEmpty(damageRgn)) {
300	CFRelease(damageRgn);
301	CFRelease(boundsRgn);
302	return 0;
303    }
304    HIShapeGetBounds(damageRgn, &damageBounds);
305    if (!Tk_IsTopLevel(winPtr)) {
306	ChkErr(TkMacOSHIShapeUnion, boundsRgn, updateRgn, updateRgn);
307	HIShapeGetBounds(updateRgn, updateBounds);
308    }
309    CFRelease(damageRgn);
310    CFRelease(boundsRgn);
311
312    event.xany.serial = LastKnownRequestProcessed(Tk_Display(winPtr));
313    event.xany.send_event = false;
314    event.xany.window = Tk_WindowId(winPtr);
315    event.xany.display = Tk_Display(winPtr);
316    event.type = Expose;
317    event.xexpose.x = damageBounds.origin.x - bounds.origin.x;
318    event.xexpose.y = damageBounds.origin.y - bounds.origin.y;
319    event.xexpose.width = damageBounds.size.width;
320    event.xexpose.height = damageBounds.size.height;
321    event.xexpose.count = 0;
322    Tk_QueueWindowEvent(&event, TCL_QUEUE_TAIL);
323#ifdef TK_MAC_DEBUG_DRAWING
324    TKLog(@"Expose %p {{%d, %d}, {%d, %d}}", event.xany.window, event.xexpose.x,
325	event.xexpose.y, event.xexpose.width, event.xexpose.height);
326#endif
327
328    /*
329     * Generate updates for the children of this window
330     */
331
332    for (childPtr = winPtr->childList; childPtr != NULL;
333	    childPtr = childPtr->nextPtr) {
334	if (!Tk_IsMapped(childPtr) || Tk_IsTopLevel(childPtr)) {
335	    continue;
336	}
337	GenerateUpdates(updateRgn, updateBounds, childPtr);
338    }
339
340    /*
341     * Generate updates for any contained windows
342     */
343
344    if (Tk_IsContainer(winPtr)) {
345	childPtr = TkpGetOtherWindow(winPtr);
346	if (childPtr != NULL && Tk_IsMapped(childPtr)) {
347	    GenerateUpdates(updateRgn, updateBounds, childPtr);
348	}
349
350	/*
351	 * TODO: Here we should handle out of process embedding.
352	 */
353    }
354
355    return 1;
356}
357
358/*
359 *----------------------------------------------------------------------
360 *
361 * GenerateActivateEvents --
362 *
363 *	Given a Macintosh window activate event this function generates all
364 *	the X Activate events needed by Tk.
365 *
366 * Results:
367 *	True if event(s) are generated - false otherwise.
368 *
369 * Side effects:
370 *	Additional events may be place on the Tk event queue.
371 *
372 *----------------------------------------------------------------------
373 */
374
375int
376GenerateActivateEvents(
377    TkWindow *winPtr,
378    int activeFlag)
379{
380    TkGenerateActivateEvents(winPtr, activeFlag);
381    TkMacOSXGenerateFocusEvent(winPtr, activeFlag);
382    TkMacOSXEnterExitFullscreen(winPtr, activeFlag);
383    return true;
384}
385
386/*
387 *----------------------------------------------------------------------
388 *
389 * DoWindowActivate --
390 *
391 *	Idle handler that calls GenerateActivateEvents().
392 *
393 * Results:
394 *	None.
395 *
396 * Side effects:
397 *	Additional events may be place on the Tk event queue.
398 *
399 *----------------------------------------------------------------------
400 */
401
402void
403DoWindowActivate(
404    ClientData clientData)
405{
406    GenerateActivateEvents(clientData, 1);
407}
408
409/*
410 *----------------------------------------------------------------------
411 *
412 * TkMacOSXGenerateFocusEvent --
413 *
414 *	Given a Macintosh window activate event this function generates all
415 *	the X Focus events needed by Tk.
416 *
417 * Results:
418 *	True if event(s) are generated - false otherwise.
419 *
420 * Side effects:
421 *	Additional events may be place on the Tk event queue.
422 *
423 *----------------------------------------------------------------------
424 */
425
426MODULE_SCOPE int
427TkMacOSXGenerateFocusEvent(
428    TkWindow *winPtr,		/* Root X window for event. */
429    int activeFlag)
430{
431    XEvent event;
432
433    /*
434     * Don't send focus events to windows of class help or to windows with the
435     * kWindowNoActivatesAttribute.
436     */
437
438    if (winPtr->wmInfoPtr && (winPtr->wmInfoPtr->macClass == kHelpWindowClass ||
439	    winPtr->wmInfoPtr->attributes & kWindowNoActivatesAttribute)) {
440	return false;
441    }
442
443    /*
444     * Generate FocusIn and FocusOut events. This event is only sent to the
445     * toplevel window.
446     */
447
448    if (activeFlag) {
449	event.xany.type = FocusIn;
450    } else {
451	event.xany.type = FocusOut;
452    }
453
454    event.xany.serial = LastKnownRequestProcessed(Tk_Display(winPtr));
455    event.xany.send_event = False;
456    event.xfocus.display = Tk_Display(winPtr);
457    event.xfocus.window = winPtr->window;
458    event.xfocus.mode = NotifyNormal;
459    event.xfocus.detail = NotifyDetailNone;
460
461    Tk_QueueWindowEvent(&event, TCL_QUEUE_TAIL);
462    return true;
463}
464
465/*
466 *----------------------------------------------------------------------
467 *
468 * TkGenWMConfigureEvent --
469 *
470 *	Generate a ConfigureNotify event for Tk. Depending on the value of
471 *	flag the values of width/height, x/y, or both may be changed.
472 *
473 * Results:
474 *	None.
475 *
476 * Side effects:
477 *	A ConfigureNotify event is sent to Tk.
478 *
479 *----------------------------------------------------------------------
480 */
481
482void
483TkGenWMConfigureEvent(
484    Tk_Window tkwin,
485    int x,
486    int y,
487    int width,
488    int height,
489    int flags)
490{
491    XEvent event;
492    WmInfo *wmPtr;
493    TkWindow *winPtr = (TkWindow *) tkwin;
494
495    if (tkwin == NULL) {
496	return;
497    }
498
499    event.type = ConfigureNotify;
500    event.xconfigure.serial = LastKnownRequestProcessed(Tk_Display(tkwin));
501    event.xconfigure.send_event = False;
502    event.xconfigure.display = Tk_Display(tkwin);
503    event.xconfigure.event = Tk_WindowId(tkwin);
504    event.xconfigure.window = Tk_WindowId(tkwin);
505    event.xconfigure.border_width = winPtr->changes.border_width;
506    event.xconfigure.override_redirect = winPtr->atts.override_redirect;
507    if (winPtr->changes.stack_mode == Above) {
508	event.xconfigure.above = winPtr->changes.sibling;
509    } else {
510	event.xconfigure.above = None;
511    }
512
513    if (!(flags & TK_LOCATION_CHANGED)) {
514	x = Tk_X(tkwin);
515	y = Tk_Y(tkwin);
516    }
517    if (!(flags & TK_SIZE_CHANGED)) {
518	width = Tk_Width(tkwin);
519	height = Tk_Height(tkwin);
520    }
521    event.xconfigure.x = x;
522    event.xconfigure.y = y;
523    event.xconfigure.width = width;
524    event.xconfigure.height = height;
525
526    if (flags & TK_MACOSX_HANDLE_EVENT_IMMEDIATELY) {
527	Tk_HandleEvent(&event);
528    } else {
529	Tk_QueueWindowEvent(&event, TCL_QUEUE_TAIL);
530    }
531
532    /*
533     * Update window manager information.
534     */
535
536    if (Tk_IsTopLevel(winPtr)) {
537	wmPtr = winPtr->wmInfoPtr;
538	if (flags & TK_LOCATION_CHANGED) {
539	    wmPtr->x = x;
540	    wmPtr->y = y;
541	    wmPtr->flags &= ~(WM_NEGATIVE_X | WM_NEGATIVE_Y);
542	}
543	if ((flags & TK_SIZE_CHANGED) && !(wmPtr->flags & WM_SYNC_PENDING) &&
544		((width != Tk_Width(tkwin)) || (height != Tk_Height(tkwin)))) {
545	    if ((wmPtr->width == -1) && (width == winPtr->reqWidth)) {
546		/*
547		 * Don't set external width, since the user didn't change it
548		 * from what the widgets asked for.
549		 */
550	    } else {
551		if (wmPtr->gridWin != NULL) {
552		    wmPtr->width = wmPtr->reqGridWidth
553			    + (width - winPtr->reqWidth)/wmPtr->widthInc;
554		    if (wmPtr->width < 0) {
555			wmPtr->width = 0;
556		    }
557		} else {
558		    wmPtr->width = width;
559		}
560	    }
561	    if ((wmPtr->height == -1) && (height == winPtr->reqHeight)) {
562		/*
563		 * Don't set external height, since the user didn't change it
564		 * from what the widgets asked for.
565		 */
566	    } else {
567		if (wmPtr->gridWin != NULL) {
568		    wmPtr->height = wmPtr->reqGridHeight
569			    + (height - winPtr->reqHeight)/wmPtr->heightInc;
570		    if (wmPtr->height < 0) {
571			wmPtr->height = 0;
572		    }
573		} else {
574		    wmPtr->height = height;
575		}
576	    }
577	    wmPtr->configWidth = width;
578	    wmPtr->configHeight = height;
579	}
580    }
581
582    /*
583     * Now set up the changes structure. Under X we wait for the
584     * ConfigureNotify to set these values. On the Mac we know imediatly that
585     * this is what we want - so we just set them. However, we need to make
586     * sure the windows clipping region is marked invalid so the change is
587     * visible to the subwindow.
588     */
589
590    winPtr->changes.x = x;
591    winPtr->changes.y = y;
592    winPtr->changes.width = width;
593    winPtr->changes.height = height;
594    TkMacOSXInvalClipRgns(tkwin);
595}
596
597/*
598 *----------------------------------------------------------------------
599 *
600 * TkGenWMDestroyEvent --
601 *
602 *	Generate a WM Destroy event for Tk.
603 *
604 * Results:
605 *	None.
606 *
607 * Side effects:
608 *	A WM_PROTOCOL/WM_DELETE_WINDOW event is sent to Tk.
609 *
610 *----------------------------------------------------------------------
611 */
612
613void
614TkGenWMDestroyEvent(
615    Tk_Window tkwin)
616{
617    XEvent event;
618
619    event.xany.serial = LastKnownRequestProcessed(Tk_Display(tkwin));
620    event.xany.send_event = False;
621    event.xany.display = Tk_Display(tkwin);
622
623    event.xclient.window = Tk_WindowId(tkwin);
624    event.xclient.type = ClientMessage;
625    event.xclient.message_type = Tk_InternAtom(tkwin, "WM_PROTOCOLS");
626    event.xclient.format = 32;
627    event.xclient.data.l[0] = Tk_InternAtom(tkwin, "WM_DELETE_WINDOW");
628    Tk_HandleEvent(&event);
629}
630
631/*
632 *----------------------------------------------------------------------
633 *
634 * TkWmProtocolEventProc --
635 *
636 *	This procedure is called by the Tk_HandleEvent whenever a
637 *	ClientMessage event arrives whose type is "WM_PROTOCOLS". This
638 *	procedure handles the message from the window manager in an
639 *	appropriate fashion.
640 *
641 * Results:
642 *	None.
643 *
644 * Side effects:
645 *	Depends on what sort of handler, if any, was set up for the protocol.
646 *
647 *----------------------------------------------------------------------
648 */
649
650void
651TkWmProtocolEventProc(
652    TkWindow *winPtr,		/* Window to which the event was sent. */
653    XEvent *eventPtr)		/* X event. */
654{
655    WmInfo *wmPtr;
656    ProtocolHandler *protPtr;
657    Tcl_Interp *interp;
658    Atom protocol;
659    int result;
660
661    wmPtr = winPtr->wmInfoPtr;
662    if (wmPtr == NULL) {
663	return;
664    }
665    protocol = (Atom) eventPtr->xclient.data.l[0];
666    for (protPtr = wmPtr->protPtr; protPtr != NULL;
667	    protPtr = protPtr->nextPtr) {
668	if (protocol == protPtr->protocol) {
669	    Tcl_Preserve(protPtr);
670	    interp = protPtr->interp;
671	    Tcl_Preserve(interp);
672	    result = Tcl_GlobalEval(interp, protPtr->command);
673	    if (result != TCL_OK) {
674		Tcl_AddErrorInfo(interp, "\n    (command for \"");
675		Tcl_AddErrorInfo(interp,
676			Tk_GetAtomName((Tk_Window) winPtr, protocol));
677		Tcl_AddErrorInfo(interp, "\" window manager protocol)");
678		Tk_BackgroundError(interp);
679	    }
680	    Tcl_Release(interp);
681	    Tcl_Release(protPtr);
682	    return;
683	}
684    }
685
686    /*
687     * No handler was present for this protocol. If this is a WM_DELETE_WINDOW
688     * message then just destroy the window.
689     */
690
691    if (protocol == Tk_InternAtom((Tk_Window) winPtr, "WM_DELETE_WINDOW")) {
692	Tk_DestroyWindow((Tk_Window) winPtr);
693    }
694}
695
696/*
697 *----------------------------------------------------------------------
698 *
699 * Tk_MacOSXIsAppInFront --
700 *
701 *	Returns 1 if this app is the foreground app.
702 *
703 * Results:
704 *	1 if app is in front, 0 otherwise.
705 *
706 * Side effects:
707 *	None.
708 *
709 *----------------------------------------------------------------------
710 */
711
712int
713Tk_MacOSXIsAppInFront(void)
714{
715    OSStatus err;
716    ProcessSerialNumber frontPsn, ourPsn = {0, kCurrentProcess};
717    Boolean isFrontProcess = true;
718
719    err = ChkErr(GetFrontProcess, &frontPsn);
720    if (err == noErr) {
721	ChkErr(SameProcess, &frontPsn, &ourPsn, &isFrontProcess);
722    }
723
724    return (isFrontProcess == true);
725}
726
727#pragma mark TKContentView
728
729#import <ApplicationServices/ApplicationServices.h>
730
731/*
732 * Custom content view for Tk NSWindows, containing standard NSView subviews.
733 * The goal is to emulate X11-style drawing in response to Expose events:
734 * during the normal AppKit drawing cycle, we supress drawing of all subviews
735 * (using a technique adapted from WebKit's WebHTMLView) and instead send
736 * Expose events about the subviews that would be redrawn.
737 * Tk Expose event handling and drawing handlers then draw the subviews
738 * manually via their -displayRectIgnoringOpacity:
739 */
740
741@interface TKContentView(TKWindowEvent)
742- (void)drawRect:(NSRect)rect;
743- (void)generateExposeEvents:(HIMutableShapeRef)shape;
744- (BOOL)isOpaque;
745- (BOOL)wantsDefaultClipping;
746- (BOOL)acceptsFirstResponder;
747- (void)keyDown:(NSEvent *)theEvent;
748@end
749
750@implementation TKContentView
751@end
752
753static Tk_RestrictAction ExposeRestrictProc(ClientData arg, XEvent *eventPtr)
754{
755    return (eventPtr->type == Expose && eventPtr->xany.serial == PTR2UINT(arg) ?
756	    TK_PROCESS_EVENT : TK_DEFER_EVENT);
757}
758
759@implementation TKContentView(TKWindowEvent)
760
761- (void)drawRect:(NSRect)rect {
762    const NSRect *rectsBeingDrawn;
763    NSInteger rectsBeingDrawnCount;
764    [self getRectsBeingDrawn:&rectsBeingDrawn count:&rectsBeingDrawnCount];
765#ifdef TK_MAC_DEBUG_DRAWING
766    TKLog(@"-[%@(%p) %s%@]", [self class], self, _cmd, NSStringFromRect(rect));
767    [[NSColor colorWithDeviceRed:0.0 green:1.0 blue:0.0 alpha:.1] setFill];
768    NSRectFillListUsingOperation(rectsBeingDrawn, rectsBeingDrawnCount,
769	    NSCompositeSourceOver);
770#endif
771    NSWindow *w = [self window];
772    if ([self isOpaque] && [w showsResizeIndicator]) {
773	NSRect bounds = [self convertRect:[w _growBoxRect] fromView:nil];
774	if ([self needsToDrawRect:bounds]) {
775	    NSEraseRect(bounds);
776	}
777    }
778    CGFloat height = [self bounds].size.height;
779    HIMutableShapeRef drawShape = HIShapeCreateMutable();
780    while (rectsBeingDrawnCount--) {
781	CGRect r = NSRectToCGRect(*rectsBeingDrawn++);
782	r.origin.y = height - (r.origin.y + r.size.height);
783	HIShapeUnionWithRect(drawShape, &r);
784    }
785    if (CFRunLoopGetMain() == CFRunLoopGetCurrent()) {
786	[self generateExposeEvents:drawShape];
787    } else {
788	[self performSelectorOnMainThread:@selector(generateExposeEvents:)
789		withObject:(id)drawShape waitUntilDone:NO
790		modes:[NSArray arrayWithObjects:NSRunLoopCommonModes,
791			NSEventTrackingRunLoopMode, NSModalPanelRunLoopMode,
792			nil]];
793    }
794    CFRelease(drawShape);
795}
796
797- (void)generateExposeEvents:(HIMutableShapeRef)shape {
798    TkWindow *winPtr = TkMacOSXGetTkWindow([self window]);
799    unsigned long serial;
800    CGRect updateBounds;
801
802    if (!winPtr) {
803	return;
804    }
805    HIShapeGetBounds(shape, &updateBounds);
806    serial = LastKnownRequestProcessed(Tk_Display(winPtr));
807    if (GenerateUpdates(shape, &updateBounds, winPtr) &&
808	    ![[NSRunLoop currentRunLoop] currentMode] &&
809	    Tcl_GetServiceMode() != TCL_SERVICE_NONE) {
810	/*
811	 * Ensure there are no pending idle-time redraws that could prevent
812	 * the just posted Expose events from generating new redraws.
813	 */
814
815	while (Tcl_DoOneEvent(TCL_IDLE_EVENTS|TCL_DONT_WAIT)) {}
816
817	/*
818	 * For smoother drawing, process Expose events and resulting redraws
819	 * immediately instead of at idle time.
820	 */
821
822	ClientData oldArg;
823	Tk_RestrictProc *oldProc = Tk_RestrictEvents(ExposeRestrictProc,
824		UINT2PTR(serial), &oldArg);
825	while (Tcl_ServiceEvent(TCL_WINDOW_EVENTS)) {}
826	Tk_RestrictEvents(oldProc, oldArg, &oldArg);
827	while (Tcl_DoOneEvent(TCL_IDLE_EVENTS|TCL_DONT_WAIT)) {}
828    }
829}
830
831- (void)tkToolbarButton:(id)sender {
832#ifdef TK_MAC_DEBUG_EVENTS
833    TKLog(@"-[%@(%p) %s] %@", [self class], self, _cmd);
834#endif
835    XVirtualEvent event;
836    int x, y;
837    TkWindow *winPtr = TkMacOSXGetTkWindow([self window]);
838    Tk_Window tkwin = (Tk_Window) winPtr;
839
840    bzero(&event, sizeof(XVirtualEvent));
841    event.type = VirtualEvent;
842    event.serial = LastKnownRequestProcessed(Tk_Display(tkwin));
843    event.send_event = false;
844    event.display = Tk_Display(tkwin);
845    event.event = Tk_WindowId(tkwin);
846    event.root = XRootWindow(Tk_Display(tkwin), 0);
847    event.subwindow = None;
848    event.time = TkpGetMS();
849    XQueryPointer(NULL, winPtr->window, NULL, NULL,
850	    &event.x_root, &event.y_root, &x, &y, &event.state);
851    Tk_TopCoordsToWindow(tkwin, x, y, &event.x, &event.y);
852    event.same_screen = true;
853    event.name = Tk_GetUid("ToolbarButton");
854    Tk_QueueWindowEvent((XEvent *) &event, TCL_QUEUE_TAIL);
855}
856
857#ifdef TK_MAC_DEBUG_DRAWING
858- (void)setFrameSize:(NSSize)newSize {
859    TKLog(@"-[%@(%p) %s%@]", [self class], self, _cmd, NSStringFromSize(newSize));
860    [super setFrameSize:newSize];
861}
862- (void)setNeedsDisplayInRect:(NSRect)invalidRect {
863    TKLog(@"-[%@(%p) %s%@]", [self class], self, _cmd,
864	    NSStringFromRect(invalidRect));
865    [super setNeedsDisplayInRect:invalidRect];
866}
867#endif
868- (BOOL)isOpaque {
869    NSWindow *w = [self window];
870
871    return (w && (([w styleMask] & NSTexturedBackgroundWindowMask) ||
872	    ![w isOpaque]) ? NO : YES);
873}
874
875- (BOOL)wantsDefaultClipping {
876    return NO;
877}
878
879- (BOOL)acceptsFirstResponder {
880    return YES;
881}
882
883- (void)keyDown:(NSEvent *)theEvent {
884#ifdef TK_MAC_DEBUG_EVENTS
885    TKLog(@"-[%@(%p) %s] %@", [self class], self, _cmd, theEvent);
886#endif
887}
888
889@end
890
891#pragma mark TKContentViewPrivate
892
893/*
894 * Technique adapted from WebKit/WebKit/mac/WebView/WebHTMLView.mm to supress
895 * normal AppKit subview drawing and make all drawing go through us.
896 * Overrides NSView internals.
897 */
898
899@interface TKContentView(TKContentViewPrivate)
900- (id)initWithFrame:(NSRect)frame;
901- (void)_setAsideSubviews;
902- (void)_restoreSubviews;
903@end
904
905@interface NSView(TKContentViewPrivate)
906- (void)_recursiveDisplayRectIfNeededIgnoringOpacity:(NSRect)rect isVisibleRect:(BOOL)isVisibleRect rectIsVisibleRectForView:(NSView *)visibleView topView:(BOOL)topView;
907- (void)_recursiveDisplayAllDirtyWithLockFocus:(BOOL)needsLockFocus visRect:(NSRect)visRect;
908- (void)_recursive:(BOOL)recurse displayRectIgnoringOpacity:(NSRect)displayRect inContext:(NSGraphicsContext *)context topView:(BOOL)topView;
909- (void)_lightWeightRecursiveDisplayInRect:(NSRect)visRect;
910- (BOOL)_drawRectIfEmpty;
911- (void)_drawRect:(NSRect)inRect clip:(BOOL)clip;
912- (void)_setDrawsOwnDescendants:(BOOL)drawsOwnDescendants;
913@end
914
915
916@implementation TKContentView(TKContentViewPrivate)
917
918- (id)initWithFrame:(NSRect)frame {
919    self = [super initWithFrame:frame];
920    if (self) {
921	_savedSubviews = nil;
922	_subviewsSetAside = NO;
923	[self _setDrawsOwnDescendants:YES];
924    }
925    return self;
926}
927
928- (void)_setAsideSubviews
929{
930#ifdef TK_MAC_DEBUG
931    if (_subviewsSetAside || _savedSubviews) {
932	Tcl_Panic("TKContentView _setAsideSubviews called incorrectly");
933    }
934#endif
935    _savedSubviews = _subviews;
936    _subviews = nil;
937    _subviewsSetAside = YES;
938 }
939
940 - (void)_restoreSubviews
941 {
942#ifdef TK_MAC_DEBUG
943    if (!_subviewsSetAside || _subviews) {
944	Tcl_Panic("TKContentView _restoreSubviews called incorrectly");
945    }
946#endif
947    _subviews = _savedSubviews;
948    _savedSubviews = nil;
949    _subviewsSetAside = NO;
950}
951
952- (void)_recursiveDisplayRectIfNeededIgnoringOpacity:(NSRect)rect isVisibleRect:(BOOL)isVisibleRect rectIsVisibleRectForView:(NSView *)visibleView topView:(BOOL)topView
953{
954    [self _setAsideSubviews];
955    [super _recursiveDisplayRectIfNeededIgnoringOpacity:rect isVisibleRect:isVisibleRect rectIsVisibleRectForView:visibleView topView:topView];
956    [self _restoreSubviews];
957}
958
959- (void)_recursiveDisplayAllDirtyWithLockFocus:(BOOL)needsLockFocus visRect:(NSRect)visRect
960{
961    BOOL needToSetAsideSubviews = !_subviewsSetAside;
962    if (needToSetAsideSubviews) {
963        [self _setAsideSubviews];
964    }
965    [super _recursiveDisplayAllDirtyWithLockFocus:needsLockFocus visRect:visRect];
966    if (needToSetAsideSubviews) {
967        [self _restoreSubviews];
968    }
969}
970
971- (void)_recursive:(BOOL)recurse displayRectIgnoringOpacity:(NSRect)displayRect inContext:(NSGraphicsContext *)context topView:(BOOL)topView
972{
973    [self _setAsideSubviews];
974    [super _recursive:recurse displayRectIgnoringOpacity:displayRect inContext:context topView:topView];
975    [self _restoreSubviews];
976}
977
978- (void)_lightWeightRecursiveDisplayInRect:(NSRect)visRect {
979    BOOL needToSetAsideSubviews = !_subviewsSetAside;
980    if (needToSetAsideSubviews) {
981        [self _setAsideSubviews];
982    }
983    [super _lightWeightRecursiveDisplayInRect:visRect];
984    if (needToSetAsideSubviews) {
985        [self _restoreSubviews];
986    }
987}
988
989- (BOOL)_drawRectIfEmpty {
990    /*
991     * Our -drawRect manages subview drawing directly, so it needs to be called
992     * even if the area to be redrawn is completely obscured by subviews.
993     */
994    return YES;
995}
996
997- (void)_drawRect:(NSRect)inRect clip:(BOOL)clip {
998#ifdef TK_MAC_DEBUG_DRAWING
999    TKLog(@"-[%@(%p) %s%@]", [self class], self, _cmd, NSStringFromRect(inRect));
1000#endif
1001    BOOL subviewsWereSetAside = _subviewsSetAside;
1002    if (subviewsWereSetAside) {
1003        [self _restoreSubviews];
1004    }
1005    [super _drawRect:inRect clip:clip];
1006    if (subviewsWereSetAside) {
1007        [self _setAsideSubviews];
1008    }
1009}
1010
1011@end
1012
1013/*
1014 * Local Variables:
1015 * mode: c
1016 * c-basic-offset: 4
1017 * fill-column: 79
1018 * coding: utf-8
1019 * End:
1020 */
1021