diff options
-rw-r--r-- | ChangeLog | 31 | ||||
-rw-r--r-- | changes | 8 | ||||
-rw-r--r-- | tests/unixWm.test | 15 | ||||
-rw-r--r-- | tests/wm.test | 133 | ||||
-rw-r--r-- | unix/tkUnixWm.c | 129 | ||||
-rw-r--r-- | win/tkWinWm.c | 98 |
6 files changed, 370 insertions, 44 deletions
@@ -1,4 +1,35 @@ +2002-06-12 Mo DeJong <mdejong@users.sourceforge.net> + + * changes: Add note about new transient behavior. + * tests/unixWm.test: Check that the WM_TRANSIENT_FOR + property for a transient window is being cleared + when the master is destroyed. + * tests/wm.test: Source defs.tcl instead of using + tcltest to match the rest of Tk's test files. + Add new tests that ensure that a transient's state + mirrors the state of the master. + * unix/tkUnixWm.c (WmInfo, TkWmNewWindow, TkWmMapWindow, + TkWmDeadWindow, Tk_WmCmd, WmWaitMapProc): Add numTransients + member to WmInfo structure. Keep state of master and + transient in sync using a callback that tracks MapNotify + and UnmapNotify events. When the master is mapped, map + the transient. When the master is unmapped or iconified, + withdraw the transient. + * win/tkWinWm.c (TkWmMapWindow, TkpWmSetState, + TkWmDeadWindow, Tk_WmCmd, WmWaitVisibilityOrMapProc): + Keep state of master and transient in sync using a + callback that tracks MapNotify and UnmapNotify events. + Move masterPtr check from TkpWmSetState into TkWmMapWindow + to deal with WM_NEVER_MAPPED transients. Cleanup + numTransients and the callback in TkWmDeadWindow. + Cleanup numTransients and the callback only after + deleting a master in wm transient command to avoid + deleting the callback when an error is raised. + Add support for MapNotify and UnmapNotify events + to the master callback. + 2002-06-11 Joe English <jenglish@users.sf.net> + * library/menu.tcl: fix for bug report #530212 "Bad Window Path Name in tkMenuFind" @@ -2,7 +2,7 @@ This file summarizes all changes made to Tk since version 1.0 was released on March 13, 1991. Changes that aren't backward compatible are marked specially. -RCS: @(#) $Id: changes,v 1.52 2002/05/27 22:54:42 mdejong Exp $ +RCS: @(#) $Id: changes,v 1.53 2002/06/12 19:02:48 mdejong Exp $ 3/16/91 (bug fix) Modified tkWindow.c to remove Tk's Tcl commands from the interpreter when the main window is deleted (otherwise there will @@ -5184,4 +5184,8 @@ address TIP 72 changes (64-bit) in Tcl (fellows) --- Released 8.4a4, March 5, 2002 --- See ChangeLog for details --- -2002-05-27 (feature change) [wm transient .t .t] now raises an error +2002-05-27 (feature change) [wm transient .t .t] now raises an error (dejong) + +2002-06-12 (feature change) A transient toplevel now mirrors state +changes in the master. (dejong) + diff --git a/tests/unixWm.test b/tests/unixWm.test index ae675a4..f22ab67 100644 --- a/tests/unixWm.test +++ b/tests/unixWm.test @@ -7,7 +7,7 @@ # Copyright (c) 1998-1999 by Scriptics Corporation. # All rights reserved. # -# RCS: @(#) $Id: unixWm.test,v 1.15 2002/05/24 09:50:11 mdejong Exp $ +# RCS: @(#) $Id: unixWm.test,v 1.16 2002/06/12 19:02:49 mdejong Exp $ if {[lsearch [namespace children] ::tcltest] == -1} { source [file join [pwd] [file dirname [info script]] defs.tcl] @@ -1279,7 +1279,18 @@ test unixWm-37.3 {Tk_WmCmd procedure, "transient" option} {unixOnly} { destroy .t2 set result } {{} {} .t 0 {} 0x0} -test unixWm-37.4 {Tk_WmCmd procedure, "transient" option, create master wrapper} {unixOnly} { +test unixWm-37.4 {TkWmDeadWindow, destroy on master should clear transient} {unixOnly} { + catch {destroy .t2} + toplevel .t2 + catch {destroy .t3} + toplevel .t3 + wm transient .t2 .t3 + update + destroy .t3 + update + list [wm transient .t2] [testprop [testwrapper .t2] WM_TRANSIENT_FOR] +} {{} 0x0} +test unixWm-37.5 {Tk_WmCmd procedure, "transient" option, create master wrapper} {unixOnly} { catch {destroy .t2} catch {destroy .t3} toplevel .t2 -width 120 -height 300 diff --git a/tests/wm.test b/tests/wm.test index 14cce6b..f6df2ab 100644 --- a/tests/wm.test +++ b/tests/wm.test @@ -7,15 +7,14 @@ # Copyright (c) 1998-1999 by Scriptics Corporation. # All rights reserved. # -# RCS: @(#) $Id: wm.test,v 1.8 2002/05/27 22:54:42 mdejong Exp $ +# RCS: @(#) $Id: wm.test,v 1.9 2002/06/12 19:02:49 mdejong Exp $ # This file tests window manager interactions that work across # platforms. Window manager tests that only work on a specific # platform should be placed in unixWm.test or winWm.test. if {[lsearch [namespace children] ::tcltest] == -1} { - package require tcltest - namespace import -force ::tcltest::* + source [file join [pwd] [file dirname [info script]] defs.tcl] } proc deleteWindows {} { @@ -381,6 +380,134 @@ test wm-transient-2.2 { first toplevel parent of wm transient .subject } {.master} +test wm-transient-3.1 { transient toplevel is withdrawn + when mapped if master is withdrawn } { + deleteWindows + toplevel .master + wm withdraw .master + update + toplevel .subject + wm transient .subject .master + update + list [wm state .subject] [winfo ismapped .subject] +} {withdrawn 0} + +test wm-transient-3.2 { already mapped transient toplevel + takes on withdrawn state of master } { + deleteWindows + toplevel .master + wm withdraw .master + update + toplevel .subject + update + wm transient .subject .master + update + list [wm state .subject] [winfo ismapped .subject] +} {withdrawn 0} + +test wm-transient-3.3 { withdraw/deiconify on the master + also does a withdraw/deiconify on the transient } { + deleteWindows + set results [list] + toplevel .master + toplevel .subject + update + wm transient .subject .master + wm withdraw .master + update + lappend results [wm state .subject] \ + [winfo ismapped .subject] + wm deiconify .master + update + lappend results [wm state .subject] \ + [winfo ismapped .subject] + set results +} {withdrawn 0 normal 1} + +test wm-transient-4.1 { transient toplevel is withdrawn + when mapped if master is iconic } { + deleteWindows + toplevel .master + wm iconify .master + update + toplevel .subject + wm transient .subject .master + update + list [wm state .subject] [winfo ismapped .subject] +} {withdrawn 0} + +test wm-transient-4.2 { already mapped transient toplevel + is withdrawn if master is iconic } { + deleteWindows + toplevel .master + wm iconify .master + update + toplevel .subject + update + wm transient .subject .master + update + list [wm state .subject] [winfo ismapped .subject] +} {withdrawn 0} + +test wm-transient-4.3 { iconify/deiconify on the master + does a withdraw/deiconify on the transient } { + deleteWindows + set results [list] + toplevel .master + toplevel .subject + update + wm transient .subject .master + wm iconify .master + update + lappend results [wm state .subject] \ + [winfo ismapped .subject] + wm deiconify .master + update + lappend results [wm state .subject] \ + [winfo ismapped .subject] + set results +} {withdrawn 0 normal 1} + +test wm-transient-5.1 { an error during transient command should not + cause the map/unmap binding to be deleted } { + deleteWindows + set results [list] + toplevel .master + toplevel .subject + update + wm transient .subject .master + # Expect a bad window path error here + lappend results [catch {wm transient .subject .bad}] + wm withdraw .master + update + lappend results [wm state .subject] + wm deiconify .master + update + lappend results [wm state .subject] + set results +} {1 withdrawn normal} + +test wm-transient-5.2 { remove transient property when master + is destroyed } { + deleteWindows + toplevel .master + toplevel .subject + wm transient .subject .master + update + destroy .master + update + wm transient .subject +} {} + +test wm-transient-5.3 { remove transient property from window + that had never been mapped when master is destroyed } { + deleteWindows + toplevel .master + toplevel .subject + wm transient .subject .master + destroy .master + wm transient .subject +} {} test wm-state-1.1 {usage} { diff --git a/unix/tkUnixWm.c b/unix/tkUnixWm.c index 50bd41a..8ab742b 100644 --- a/unix/tkUnixWm.c +++ b/unix/tkUnixWm.c @@ -12,7 +12,7 @@ * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * - * RCS: @(#) $Id: tkUnixWm.c,v 1.20 2002/05/27 22:54:42 mdejong Exp $ + * RCS: @(#) $Id: tkUnixWm.c,v 1.21 2002/06/12 19:02:49 mdejong Exp $ */ #include "tkPort.h" @@ -197,6 +197,7 @@ typedef struct TkWmInfo { char *clientMachine; /* String to store in WM_CLIENT_MACHINE * property, or NULL. */ int flags; /* Miscellaneous flags, defined below. */ + int numTransients; /* number of transients on this window */ struct TkWmInfo *nextPtr; /* Next in list of all top-level windows. */ } WmInfo; @@ -341,6 +342,8 @@ static Tk_RestrictAction XEvent *eventPtr)); static void WrapperEventProc _ANSI_ARGS_((ClientData clientData, XEvent *eventPtr)); +static void WmWaitMapProc _ANSI_ARGS_(( + ClientData clientData, XEvent *eventPtr)); /* *-------------------------------------------------------------- @@ -431,6 +434,7 @@ TkWmNewWindow(winPtr) wmPtr->winPtr = winPtr; wmPtr->reparent = None; wmPtr->masterPtr = NULL; + wmPtr->numTransients = 0; wmPtr->hints.flags = InputHint | StateHint; wmPtr->hints.input = True; wmPtr->hints.initial_state = NormalState; @@ -549,8 +553,17 @@ TkWmMapWindow(winPtr) } if (wmPtr->masterPtr != NULL) { - XSetTransientForHint(winPtr->display, wmPtr->wrapperPtr->window, - wmPtr->masterPtr->wmInfoPtr->wrapperPtr->window); + /* + * Don't map a transient if the master is not mapped. + */ + + if (!Tk_IsMapped(wmPtr->masterPtr)) { + wmPtr->withdrawn = 1; + wmPtr->hints.initial_state = WithdrawnState; + } else { + XSetTransientForHint(winPtr->display, wmPtr->wrapperPtr->window, + wmPtr->masterPtr->wmInfoPtr->wrapperPtr->window); + } } wmPtr->flags |= WM_UPDATE_SIZE_HINTS; @@ -743,6 +756,44 @@ TkWmDeadWindow(winPtr) if (wmPtr->flags & WM_UPDATE_PENDING) { Tcl_CancelIdleCall(UpdateGeometryInfo, (ClientData) winPtr); } + /* + * Reset all transient windows whose master is the dead window. + */ + + for (wmPtr2 = winPtr->dispPtr->firstWmPtr; wmPtr2 != NULL; + wmPtr2 = wmPtr2->nextPtr) { + if (wmPtr2->masterPtr == winPtr) { + wmPtr->numTransients--; + Tk_DeleteEventHandler((Tk_Window) wmPtr2->masterPtr, + StructureNotifyMask, + WmWaitMapProc, (ClientData) wmPtr2->winPtr); + wmPtr2->masterPtr = NULL; + if (!(wmPtr2->flags & WM_NEVER_MAPPED)) { + XSetTransientForHint(wmPtr2->winPtr->display, + wmPtr2->wrapperPtr->window, None); + /* FIXME: Need a call like Win32's UpdateWrapper() so + we can recreate the wrapper and get rid of the + transient window decorations. */ + } + } + } + if (wmPtr->numTransients != 0) + panic("numTransients should be 0"); + + if (wmPtr->masterPtr != NULL) { + wmPtr2 = wmPtr->masterPtr->wmInfoPtr; + /* + * If we had a master, tell them that we aren't tied + * to them anymore + */ + if (wmPtr2 != NULL) { + wmPtr2->numTransients--; + } + Tk_DeleteEventHandler((Tk_Window) wmPtr->masterPtr, + StructureNotifyMask, + WmWaitMapProc, (ClientData) winPtr); + wmPtr->masterPtr = NULL; + } ckfree((char *) wmPtr); winPtr->wmInfoPtr = NULL; } @@ -1978,6 +2029,21 @@ Tk_WmCmd(clientData, interp, argc, argv) return TCL_OK; } if (argv[3][0] == '\0') { + if (masterPtr != NULL) { + /* + * If we had a master, tell them that we aren't tied + * to them anymore + */ + masterPtr->wmInfoPtr->numTransients--; + Tk_DeleteEventHandler((Tk_Window) masterPtr, + StructureNotifyMask, + WmWaitMapProc, (ClientData) winPtr); + + /* FIXME: Need a call like Win32's UpdateWrapper() so + we can recreate the wrapper and get rid of the + transient window decorations. */ + } + wmPtr->masterPtr = NULL; } else { masterPtr = (TkWindow *) Tk_NameToWindow(interp, argv[3], tkwin); @@ -2020,14 +2086,41 @@ Tk_WmCmd(clientData, interp, argc, argv) (char *) NULL); return TCL_ERROR; } else if (masterPtr != wmPtr->masterPtr) { + /* + * Remove old master map/unmap binding before setting + * the new master. The event handler will ensure that + * transient states reflect the state of the master. + */ + + if (wmPtr->masterPtr == NULL) { + masterPtr->wmInfoPtr->numTransients++; + } else { + Tk_DeleteEventHandler((Tk_Window) wmPtr->masterPtr, + StructureNotifyMask, + WmWaitMapProc, (ClientData) winPtr); + } + + Tk_CreateEventHandler((Tk_Window) masterPtr, + StructureNotifyMask, + WmWaitMapProc, (ClientData) winPtr); + wmPtr->masterPtr = masterPtr; } } if (!(wmPtr->flags & WM_NEVER_MAPPED)) { - Window xwin = (wmPtr->masterPtr == NULL) ? None : - wmPtr->masterPtr->wmInfoPtr->wrapperPtr->window; - XSetTransientForHint(winPtr->display, wmPtr->wrapperPtr->window, - xwin); + if (wmPtr->masterPtr != NULL && !Tk_IsMapped(wmPtr->masterPtr)) { + if (TkpWmSetState(winPtr, WithdrawnState) == 0) { + Tcl_SetResult(interp, + "couldn't send withdraw message to window manager", + TCL_STATIC); + return TCL_ERROR; + } + } else { + Window xwin = (wmPtr->masterPtr == NULL) ? None : + wmPtr->masterPtr->wmInfoPtr->wrapperPtr->window; + XSetTransientForHint(winPtr->display, wmPtr->wrapperPtr->window, + xwin); + } } } else if ((c == 'w') && (strncmp(argv[1], "withdraw", length) == 0) && (length >= 2)) { @@ -2068,6 +2161,28 @@ Tk_WmCmd(clientData, interp, argc, argv) } return TCL_OK; } + +/* + * Invoked when a MapNotify or UnmapNotify event is delivered for a + * toplevel that is the master of a transient toplevel. + */ +static void +WmWaitMapProc(clientData, eventPtr) + ClientData clientData; /* Pointer to window. */ + XEvent *eventPtr; /* Information about event. */ +{ + TkWindow *winPtr = (TkWindow *) clientData; + TkWindow *masterPtr = winPtr->wmInfoPtr->masterPtr; + + if (masterPtr == NULL) + return; + + if (eventPtr->type == MapNotify) { + (void) TkpWmSetState(winPtr, NormalState); + } else if (eventPtr->type == UnmapNotify) { + (void) TkpWmSetState(winPtr, WithdrawnState); + } +} /* *---------------------------------------------------------------------- diff --git a/win/tkWinWm.c b/win/tkWinWm.c index 207dc4f..1fd42a8 100644 --- a/win/tkWinWm.c +++ b/win/tkWinWm.c @@ -12,7 +12,7 @@ * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * - * RCS: @(#) $Id: tkWinWm.c,v 1.38 2002/05/27 22:54:42 mdejong Exp $ + * RCS: @(#) $Id: tkWinWm.c,v 1.39 2002/06/12 19:02:50 mdejong Exp $ */ #include "tkWinInt.h" @@ -392,7 +392,7 @@ static void UpdateGeometryInfo _ANSI_ARGS_(( static void UpdateWrapper _ANSI_ARGS_((TkWindow *winPtr)); static LRESULT CALLBACK WmProc _ANSI_ARGS_((HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)); -static void WmWaitVisibilityProc _ANSI_ARGS_(( +static void WmWaitVisibilityOrMapProc _ANSI_ARGS_(( ClientData clientData, XEvent *eventPtr)); static BlockOfIconImagesPtr ReadIconFromICOFile _ANSI_ARGS_(( Tcl_Interp *interp, char* fileName)); @@ -1521,10 +1521,10 @@ UpdateWrapper(winPtr) if (winPtr->flags & TK_EMBEDDED) { wmPtr->wrapper = (HWND) winPtr->privatePtr; if (wmPtr->wrapper == NULL) { - panic("TkWmMapWindow: Cannot find container window"); + panic("UpdateWrapper: Cannot find container window"); } if (!IsWindow(wmPtr->wrapper)) { - panic("TkWmMapWindow: Container was destroyed"); + panic("UpdateWrapper: Container was destroyed"); } } else { @@ -1790,7 +1790,17 @@ TkWmMapWindow(winPtr) InitWm(); } - if (!(wmPtr->flags & WM_NEVER_MAPPED)) { + if (wmPtr->flags & WM_NEVER_MAPPED) { + /* + * Don't map a transient if the master is not mapped. + */ + + if (wmPtr->masterPtr != NULL && + !Tk_IsMapped(wmPtr->masterPtr)) { + wmPtr->hints.initial_state = WithdrawnState; + return; + } + } else { if (wmPtr->hints.initial_state == WithdrawnState) { return; } @@ -1867,8 +1877,7 @@ TkpWmSetState(winPtr, state) WmInfo *wmPtr = winPtr->wmInfoPtr; int cmd; - if ((wmPtr->flags & WM_NEVER_MAPPED) || - (wmPtr->masterPtr && !Tk_IsMapped(wmPtr->masterPtr))) { + if (wmPtr->flags & WM_NEVER_MAPPED) { wmPtr->hints.initial_state = state; return; } @@ -1944,6 +1953,10 @@ TkWmDeadWindow(winPtr) for (wmPtr2 = winPtr->dispPtr->firstWmPtr; wmPtr2 != NULL; wmPtr2 = wmPtr2->nextPtr) { if (wmPtr2->masterPtr == winPtr) { + wmPtr->numTransients--; + Tk_DeleteEventHandler((Tk_Window) wmPtr2->masterPtr, + VisibilityChangeMask|StructureNotifyMask, + WmWaitVisibilityOrMapProc, (ClientData) wmPtr2->winPtr); wmPtr2->masterPtr = NULL; if ((wmPtr2->wrapper != None) && !(wmPtr2->flags & (WM_NEVER_MAPPED))) { @@ -1951,6 +1964,8 @@ TkWmDeadWindow(winPtr) } } } + if (wmPtr->numTransients != 0) + panic("numTransients should be 0"); if (wmPtr->hints.flags & IconPixmapHint) { Tk_FreeBitmap(winPtr->display, wmPtr->hints.icon_pixmap); @@ -1996,8 +2011,8 @@ TkWmDeadWindow(winPtr) wmPtr2->numTransients--; } Tk_DeleteEventHandler((Tk_Window) wmPtr->masterPtr, - VisibilityChangeMask, - WmWaitVisibilityProc, (ClientData) winPtr); + VisibilityChangeMask|StructureNotifyMask, + WmWaitVisibilityOrMapProc, (ClientData) winPtr); wmPtr->masterPtr = NULL; } @@ -3274,17 +3289,19 @@ Tk_WmCmd(clientData, interp, argc, argv) } return TCL_OK; } - if (masterPtr != NULL) { - /* - * If we had a master, tell them that we aren't tied - * to them anymore - */ - masterPtr->wmInfoPtr->numTransients--; - Tk_DeleteEventHandler((Tk_Window) masterPtr, - VisibilityChangeMask, - WmWaitVisibilityProc, (ClientData) winPtr); - } if (argv[3][0] == '\0') { + if (masterPtr != NULL) { + /* + * If we had a master, tell them that we aren't tied + * to them anymore + */ + + masterPtr->wmInfoPtr->numTransients--; + Tk_DeleteEventHandler((Tk_Window) masterPtr, + VisibilityChangeMask|StructureNotifyMask, + WmWaitVisibilityOrMapProc, (ClientData) winPtr); + } + wmPtr->masterPtr = NULL; } else { masterPtr = (TkWindow*) Tk_NameToWindow(interp, argv[3], tkwin); @@ -3324,23 +3341,35 @@ Tk_WmCmd(clientData, interp, argc, argv) (char *) NULL); return TCL_ERROR; } else if (masterPtr != wmPtr->masterPtr) { - wmPtr->masterPtr = masterPtr; - masterPtr->wmInfoPtr->numTransients++; - /* - * Bind a visibility event handler to the master window, - * to ensure that when it is mapped, the children will - * have their state set properly. + * Remove old master map/unmap binding before setting + * the new master. The event handler will ensure that + * transient states reflect the state of the master. */ + if (wmPtr->masterPtr == NULL) { + masterPtr->wmInfoPtr->numTransients++; + } else { + Tk_DeleteEventHandler((Tk_Window) wmPtr->masterPtr, + VisibilityChangeMask|StructureNotifyMask, + WmWaitVisibilityOrMapProc, (ClientData) winPtr); + } + Tk_CreateEventHandler((Tk_Window) masterPtr, - VisibilityChangeMask, - WmWaitVisibilityProc, (ClientData) winPtr); + VisibilityChangeMask|StructureNotifyMask, + WmWaitVisibilityOrMapProc, (ClientData) winPtr); + + wmPtr->masterPtr = masterPtr; } } if (!((wmPtr->flags & WM_NEVER_MAPPED) && !(winPtr->flags & TK_EMBEDDED))) { - UpdateWrapper(winPtr); + if (wmPtr->masterPtr != NULL && + !Tk_IsMapped(wmPtr->masterPtr)) { + TkpWmSetState(winPtr, WithdrawnState); + } else { + UpdateWrapper(winPtr); + } } } else if ((c == 'w') && (strncmp(argv[1], "withdraw", length) == 0)) { if (argc != 3) { @@ -3377,14 +3406,23 @@ Tk_WmCmd(clientData, interp, argc, argv) } /*ARGSUSED*/ static void -WmWaitVisibilityProc(clientData, eventPtr) +WmWaitVisibilityOrMapProc(clientData, eventPtr) ClientData clientData; /* Pointer to window. */ XEvent *eventPtr; /* Information about event. */ { TkWindow *winPtr = (TkWindow *) clientData; TkWindow *masterPtr = winPtr->wmInfoPtr->masterPtr; - if ((eventPtr->type == VisibilityNotify) && (masterPtr != NULL)) { + if (masterPtr == NULL) + return; + + if (eventPtr->type == MapNotify) { + TkpWmSetState(winPtr, NormalState); + } else if (eventPtr->type == UnmapNotify) { + TkpWmSetState(winPtr, WithdrawnState); + } + + if (eventPtr->type == VisibilityNotify) { int state = masterPtr->wmInfoPtr->hints.initial_state; if ((state == NormalState) || (state == ZoomState)) { |