1/*
2 * tkMacOSXMenus.c --
3 *
4 *	These calls set up and manage the menubar for the
5 *	Macintosh version of Tk.
6 *
7 * Copyright (c) 1995-1996 Sun Microsystems, Inc.
8 * Copyright 2001, Apple Computer, Inc.
9 * Copyright (c) 2005-2007 Daniel A. Steffen <das@users.sourceforge.net>
10 *
11 * See the file "license.terms" for information on usage and redistribution
12 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
13 *
14 * RCS: @(#) $Id: tkMacOSXMenus.c,v 1.2.2.16 2007/11/09 06:26:56 das Exp $
15 */
16
17#include "tkMacOSXPrivate.h"
18
19#define kAppleMenu		256
20#define kAppleAboutItem		1
21#define kFileMenu		2
22#define kEditMenu		3
23
24#define kSourceItem		1
25#define kDemoItem		2
26#define kCloseItem		3
27
28#define EDIT_CUT		1
29#define EDIT_COPY		2
30#define EDIT_PASTE		3
31#define EDIT_CLEAR		4
32
33MenuRef tkAppleMenu;
34MenuRef tkFileMenu;
35MenuRef tkEditMenu;
36
37static Tcl_Interp * gInterp = NULL;	    /* Standard menu interpreter. */
38static EventHandlerRef menuEventHandlerRef = NULL;
39
40static void GenerateEditEvent(int flag);
41static Tcl_Obj* GetWidgetDemoPath(Tcl_Interp *interp);
42static OSStatus MenuEventHandlerProc(EventHandlerCallRef callRef,
43	EventRef event, void *userData);
44
45
46/*
47 *----------------------------------------------------------------------
48 *
49 * GetWidgetDemoPath --
50 *
51 *	Get path to the widget demo.
52 *
53 * Results:
54 *	pathObj with ref count 0.
55 *
56 * Side effects:
57 *	None.
58 *
59 *----------------------------------------------------------------------
60 */
61
62Tcl_Obj*
63GetWidgetDemoPath(
64    Tcl_Interp *interp)
65{
66    Tcl_Obj *libpath , *result = NULL;
67
68    libpath = Tcl_GetVar2Ex(gInterp, "tk_library", NULL, TCL_GLOBAL_ONLY);
69    if (libpath) {
70	Tcl_Obj *demo[2] = {	Tcl_NewStringObj("demos", 5),
71				Tcl_NewStringObj("widget", 6) };
72
73	Tcl_IncrRefCount(libpath);
74	result = Tcl_FSJoinToPath(libpath, 2, demo);
75	Tcl_DecrRefCount(libpath);
76    }
77    return result;
78}
79
80/*
81 *----------------------------------------------------------------------
82 *
83 * TkMacOSXHandleMenuSelect --
84 *
85 *	Handles events that occur in the Menu bar.
86 *
87 * Results:
88 *	None.
89 *
90 * Side effects:
91 *	None.
92 *
93 *----------------------------------------------------------------------
94 */
95
96void
97TkMacOSXHandleMenuSelect(
98    MenuID theMenu,
99    MenuItemIndex theItem,
100    int optionKeyPressed)
101{
102    Tk_Window tkwin;
103    Window window;
104    TkDisplay *dispPtr;
105
106    if (theItem == 0) {
107	TkMacOSXClearMenubarActive();
108	return;
109    }
110
111    switch (theMenu) {
112	case kAppleMenu:
113	    switch (theItem) {
114		case kAppleAboutItem:
115		    {
116			Tcl_CmdInfo dummy;
117			if (optionKeyPressed || gInterp == NULL ||
118			    Tcl_GetCommandInfo(gInterp,
119				"tkAboutDialog", &dummy) == 0) {
120			    TkAboutDlg();
121			} else {
122			    if (Tcl_EvalEx(gInterp, "tkAboutDialog", -1,
123				    TCL_EVAL_GLOBAL) != TCL_OK) {
124				Tcl_BackgroundError(gInterp);
125			    }
126			    Tcl_ResetResult(gInterp);
127			}
128			break;
129		    }
130	    }
131	    break;
132	case kFileMenu:
133	    switch (theItem) {
134		case kSourceItem:
135		    if (gInterp) {
136			if(Tcl_EvalEx(gInterp, "tk_getOpenFile -filetypes {"
137				"{{TCL Scripts} {.tcl} TEXT} "
138				"{{Text Files} {} TEXT}}", -1, TCL_EVAL_GLOBAL)
139				== TCL_OK) {
140			    Tcl_Obj *path = Tcl_GetObjResult(gInterp);
141			    int len;
142
143			    Tcl_GetStringFromObj(path, &len);
144			    if (len) {
145				Tcl_IncrRefCount(path);
146				if (Tcl_FSEvalFile(gInterp, path)
147					== TCL_ERROR) {
148				    Tcl_BackgroundError(gInterp);
149				}
150				Tcl_DecrRefCount(path);
151			    }
152			}
153			Tcl_ResetResult(gInterp);
154		    }
155		    break;
156		case kDemoItem:
157		    if (gInterp) {
158			Tcl_Obj *path = GetWidgetDemoPath(gInterp);
159
160			if (path) {
161			    Tcl_IncrRefCount(path);
162			    if (Tcl_FSEvalFile(gInterp, path)
163				    == TCL_ERROR) {
164				Tcl_BackgroundError(gInterp);
165			    }
166			    Tcl_DecrRefCount(path);
167			    Tcl_ResetResult(gInterp);
168			}
169		    }
170		    break;
171		case kCloseItem:
172		    /* Send close event */
173		    window = TkMacOSXGetXWindow(ActiveNonFloatingWindow());
174		    dispPtr = TkGetDisplayList();
175		    tkwin = Tk_IdToWindow(dispPtr->display, window);
176		    TkGenWMDestroyEvent(tkwin);
177		    break;
178	    }
179	    break;
180	case kEditMenu:
181	    /*
182	     * This implementation just send the keysyms Tk thinks are
183	     * associated with function keys that do Cut, Copy & Paste on
184	     * a Sun keyboard.
185	     */
186	    GenerateEditEvent(theItem);
187	    break;
188	default:
189	    TkMacOSXDispatchMenuEvent(theMenu, theItem);
190	    break;
191    }
192    /*
193     * Finally we unhighlight the menu.
194     */
195    HiliteMenu(0);
196}
197
198/*
199 *----------------------------------------------------------------------
200 *
201 * MenuEventHandlerProc --
202 *
203 *	One-time handler of kEventMenuEnableItems for the edit menu.
204 *
205 * Results:
206 *	OS status code.
207 *
208 * Side effects:
209 *	None.
210 *
211 *----------------------------------------------------------------------
212 */
213
214static OSStatus
215MenuEventHandlerProc(
216    EventHandlerCallRef callRef,
217    EventRef event,
218    void *userData)
219{
220    OSStatus result = eventNotHandledErr, err;
221    int menuContext;
222
223    err = ChkErr(GetEventParameter, event, kEventParamMenuContext, typeUInt32,
224	    NULL, sizeof(menuContext), NULL, &menuContext);
225    if (err == noErr && (menuContext & kMenuContextMenuBarTracking)) {
226	if (gInterp) {
227	    Tcl_Obj *path = GetWidgetDemoPath(gInterp);
228
229	    if (path) {
230		Tcl_IncrRefCount(path);
231		if (Tcl_FSAccess(path, R_OK) == 0) {
232		    EnableMenuItem(tkFileMenu, kDemoItem);
233		}
234		Tcl_DecrRefCount(path);
235	    }
236	}
237	ChkErr(RemoveEventHandler, menuEventHandlerRef);
238	menuEventHandlerRef = NULL;
239	result = noErr;
240    }
241
242    return result;
243}
244
245/*
246 *----------------------------------------------------------------------
247 *
248 * TkMacOSXInitMenus --
249 *
250 *	This procedure initializes the Macintosh menu bar.
251 *
252 * Results:
253 *	None.
254 *
255 * Side effects:
256 *	None.
257 *
258 *----------------------------------------------------------------------
259 */
260
261void
262TkMacOSXInitMenus(
263    Tcl_Interp *interp)
264{
265    OSStatus err;
266    EventHandlerUPP menuEventHandlerUPP;
267    const EventTypeSpec menuEventTypes[] = {
268	{kEventClassMenu, kEventMenuEnableItems},
269    };
270
271    gInterp = interp;
272    if (TkMacOSXUseMenuID(kAppleMenu) != TCL_OK) {
273	Tcl_Panic("Menu ID %d is already in use!", kAppleMenu);
274    }
275    err = ChkErr(CreateNewMenu, kAppleMenu, kMenuAttrDoNotUseUserCommandKeys,
276	    &tkAppleMenu);
277    if (err != noErr) {
278	Tcl_Panic("CreateNewMenu failed !");
279    }
280    SetMenuTitle(tkAppleMenu, "\p\024");
281    InsertMenu(tkAppleMenu, 0);
282    AppendMenu(tkAppleMenu, "\pAbout Tcl & Tk\xc9");
283    AppendMenu(tkAppleMenu, "\p(-");
284
285    if (TkMacOSXUseMenuID(kFileMenu) != TCL_OK) {
286	Tcl_Panic("Menu ID %d is already in use!", kFileMenu);
287    }
288    err = ChkErr(CreateNewMenu, kFileMenu, kMenuAttrDoNotUseUserCommandKeys,
289	    &tkFileMenu);
290    if (err != noErr) {
291	Tcl_Panic("CreateNewMenu failed !");
292    }
293    SetMenuTitle(tkFileMenu, "\pFile");
294    InsertMenu(tkFileMenu, 0);
295    InsertMenuItem(tkFileMenu, "\pSource\xc9", kSourceItem - 1);
296    InsertMenuItem(tkFileMenu, "\pRun Widget Demo", kDemoItem - 1);
297    InsertMenuItem(tkFileMenu, "\pClose/W", kCloseItem - 1);
298    DisableMenuItem(tkFileMenu, kDemoItem);
299    menuEventHandlerUPP = NewEventHandlerUPP(MenuEventHandlerProc);
300    ChkErr(InstallEventHandler, GetMenuEventTarget(tkFileMenu),
301	    menuEventHandlerUPP, GetEventTypeCount(menuEventTypes),
302	    menuEventTypes, NULL, &menuEventHandlerRef);
303    DisposeEventHandlerUPP(menuEventHandlerUPP);
304
305    if (TkMacOSXUseMenuID(kEditMenu) != TCL_OK) {
306	Tcl_Panic("Menu ID %d is already in use!", kEditMenu);
307    }
308    err = ChkErr(CreateNewMenu, kEditMenu, kMenuAttrDoNotUseUserCommandKeys,
309	    &tkEditMenu);
310    if (err != noErr) {
311	Tcl_Panic("CreateNewMenu failed !");
312    }
313    SetMenuTitle(tkEditMenu, "\pEdit");
314    InsertMenu(tkEditMenu, 0);
315    AppendMenu(tkEditMenu, "\pCut/X");
316    AppendMenu(tkEditMenu, "\pCopy/C");
317    AppendMenu(tkEditMenu, "\pPaste/V");
318    AppendMenu(tkEditMenu, "\pClear");
319    if (TkMacOSXUseMenuID(kHMHelpMenuID) != TCL_OK) {
320	Tcl_Panic("Help menu ID %s is already in use!", kHMHelpMenuID);
321    }
322
323    /*
324     * Workaround a Carbon bug with kHICommandPreferences: the first call to
325     * IsMenuKeyEvent returns false for the preferences menu item key shorcut
326     * event (even if the corresponding menu item is dynamically enabled by a
327     * kEventCommandUpdateStatus handler), unless the kHICommandPreferences
328     * menu item has previously been enabled manually. [Bug 1481503]
329     */
330    EnableMenuCommand(NULL, kHICommandPreferences);
331
332    DrawMenuBar();
333    return;
334}
335
336/*
337 *----------------------------------------------------------------------
338 *
339 * GenerateEditEvent --
340 *
341 *	Takes an edit menu item and posts the corasponding a virtual
342 *	event to Tk's event queue.
343 *
344 * Results:
345 *	None.
346 *
347 * Side effects:
348 *	May place events of queue.
349 *
350 *----------------------------------------------------------------------
351 */
352
353static void
354GenerateEditEvent(
355    int flag)
356{
357    XVirtualEvent event;
358    int x, y;
359    Tk_Window tkwin;
360    Window window;
361    TkDisplay *dispPtr;
362
363    window = TkMacOSXGetXWindow(ActiveNonFloatingWindow());
364    dispPtr = TkGetDisplayList();
365    tkwin = Tk_IdToWindow(dispPtr->display, window);
366    tkwin = (Tk_Window) ((TkWindow *) tkwin)->dispPtr->focusPtr;
367    if (tkwin == NULL) {
368	return;
369    }
370
371    bzero(&event, sizeof(XVirtualEvent));
372    event.type = VirtualEvent;
373    event.serial = Tk_Display(tkwin)->request;
374    event.send_event = false;
375    event.display = Tk_Display(tkwin);
376    event.event = Tk_WindowId(tkwin);
377    event.root = XRootWindow(Tk_Display(tkwin), 0);
378    event.subwindow = None;
379    event.time = TkpGetMS();
380
381    XQueryPointer(NULL, None, NULL, NULL,
382	    &event.x_root, &event.y_root, &x, &y, &event.state);
383    tkwin = Tk_TopCoordsToWindow(tkwin, x, y, &event.x, &event.y);
384    event.same_screen = true;
385
386    switch (flag) {
387	case EDIT_CUT:
388	    event.name = Tk_GetUid("Cut");
389	    break;
390	case EDIT_COPY:
391	    event.name = Tk_GetUid("Copy");
392	    break;
393	case EDIT_PASTE:
394	    event.name = Tk_GetUid("Paste");
395	    break;
396	case EDIT_CLEAR:
397	    event.name = Tk_GetUid("Clear");
398	    break;
399    }
400    Tk_QueueWindowEvent((XEvent *) &event, TCL_QUEUE_TAIL);
401}
402