diff --git a/CMakeLists.txt b/CMakeLists.txt
index 62b549c54c..927eb1a83f 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -49,6 +49,7 @@ add_subdirectory(Samples/Raytracer)
if(CMAKE_SYSTEM_NAME MATCHES Retro68)
add_subdirectory(Samples/Launcher)
add_subdirectory(Samples/SystemExtension)
+add_subdirectory(Samples/WDEF)
endif()
enable_testing()
diff --git a/README.md b/README.md
index f74f817ab1..e1ebafd0f9 100644
--- a/README.md
+++ b/README.md
@@ -301,6 +301,10 @@ Intended for System 6 without Multifinder.
Shows a simple and useless dialog box. Demonstrates how to use Rez, the resource compiler.
The binary is in Retro68-build/build-target/Samples/Dialog/.
+### Sample Program: WDEF
+
+On the one hand, this is an example for a very basic multi window application with menus and desk accessories.
+On the other hand, it shows how to write code resources like WDEF window definition procedures.
License
-------
diff --git a/Samples/WDEF/CMakeLists.txt b/Samples/WDEF/CMakeLists.txt
new file mode 100644
index 0000000000..d200267854
--- /dev/null
+++ b/Samples/WDEF/CMakeLists.txt
@@ -0,0 +1,66 @@
+# Copyright 2015 Wolfgang Thaller.
+#
+# This file is part of Retro68.
+#
+# Retro68 is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Retro68 is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Retro68. If not, see .
+
+# To use this example as a standalone project using CMake:
+# mkdir build
+# cd build
+# cmake .. -DCMAKE_TOOLCHAIN_FILE=path/to/Retro68-build/toolchain/cmake/retro68.toolchain.cmake
+# make
+
+cmake_minimum_required(VERSION 2.8)
+
+# This sample program contains a custom window definition procedure (WDEF) defined in wdef.c.
+# It is used in two different ways:
+# 1. The 80s way: compiled as a separate 'WDEF' code resource.
+# 2. The 90s way: compiled as part of the application.
+
+# First, let's build a separate code resource:
+add_executable(WDEF wdef.c)
+set_target_properties(WDEF PROPERTIES
+ # tell wdef.c that it is being compiled as a code resource
+ COMPILE_DEFINITIONS "COMPILING_AS_CODE_RESOURCE"
+
+ COMPILE_OPTIONS -ffunction-sections # make things smaller
+
+ # set a linker flag that says we want a flat piece
+ # of code in a data file, specify entry point,
+ # and add -Wl,-gc-sections to make things smaller.
+ LINK_FLAGS "-Wl,--mac-flat -Wl,-eMYWINDOWDEFPROC -Wl,-gc-sections")
+
+ # wrap the compiled WDEF into a resource
+add_custom_command(
+ OUTPUT WDEF.rsrc.bin
+ COMMAND ${REZ} -I ${REZ_INCLUDE_PATH}
+ ${CMAKE_CURRENT_SOURCE_DIR}/wdef.r
+ -o WDEF.rsrc.bin
+
+ DEPENDS WDEF wdef.r)
+
+# Now build the application
+add_application(WDEFShell
+ wdefshell.c
+ wdefshell.r
+
+ wdef.c # the WDEF as a plain source file in the application
+
+ # the separately compiled WDEF resource
+ ${CMAKE_CURRENT_BINARY_DIR}/WDEF.rsrc.bin
+ )
+
+# Again, add some options to make things smaller.
+set_target_properties(Dialog PROPERTIES COMPILE_OPTIONS -ffunction-sections)
+set_target_properties(Dialog PROPERTIES LINK_FLAGS "-Wl,-gc-sections")
diff --git a/Samples/WDEF/README.md b/Samples/WDEF/README.md
new file mode 100644
index 0000000000..8350c463c0
--- /dev/null
+++ b/Samples/WDEF/README.md
@@ -0,0 +1,9 @@
+WDEF sample program
+===================
+
+This sample program serves several purposes.
+
+1. It shows how to write a basic classic Mac application with windows, menus and desk accessories.
+2. It shows how to write a very primitive custom window definition (WDEF).
+3. It shows how to compile a WDEF (or a similar code resource) and include it in an application.
+4. It shows how to avoid 3. and include the window definition procedure directly in the application.
diff --git a/Samples/WDEF/wdef.c b/Samples/WDEF/wdef.c
new file mode 100644
index 0000000000..bf471f9e51
--- /dev/null
+++ b/Samples/WDEF/wdef.c
@@ -0,0 +1,105 @@
+#include
+#include
+#include
+
+#ifdef COMPILING_AS_CODE_RESOURCE
+#include
+#include
+#endif
+
+pascal long MyWindowDefProc(short varCode, WindowRef window, short message, long param)
+{
+#ifdef COMPILING_AS_CODE_RESOURCE
+ // If we are inside a code resource, we have two problems.
+ // First, our machine code doesn't yet know where in RAM it is located, so things
+ // will crash as soon as we call a function or access a global variable.
+ // The following call, part of libretro, fixes that:
+ RETRO68_RELOCATE();
+
+ // Next, Quickdraw's global variables are stored as part of the application's
+ // global variables. If we acces "qd.", we'll get our own copy, which QuickDraw knows
+ // nothing about. So, let's get a pointer to the real thing:
+ QDGlobalsPtr qdPtr = (QDGlobalsPtr)( (*(Ptr*)LMGetCurrentA5()) + 4 - sizeof(QDGlobals) );
+
+ // alternatively, we could just avoid accessing QuickDraw globals. In our case, that would mean
+ // using GetPort instead of qdPtr->thePort, and not using qdPtr->white and qdPtr->ltGray.
+#else
+ // We're part of the real application, we could be using qd. in the first place:
+ QDGlobalsPtr qdPtr = &qd;
+#endif
+
+ WindowPeek peek = (WindowPeek) window;
+ switch(message)
+ {
+ case kWindowMsgDraw:
+ {
+ RgnHandle rgn = NewRgn();
+ DiffRgn(peek->strucRgn, peek->contRgn, rgn);
+ FillRgn(rgn, peek->hilited ? &qdPtr->white : &qdPtr->ltGray);
+ Point savePen = qdPtr->thePort->pnSize;
+ if(peek->hilited)
+ PenSize(3,3);
+ FrameRgn(rgn);
+ PenSize(savePen.h, savePen.v);
+ DisposeRgn(rgn);
+
+ short saveFace = qdPtr->thePort->txFace;
+
+ Rect r = (*peek->contRgn)->rgnBBox;
+
+ MoveTo(r.left, r.top - 10);
+ HLock((Handle) peek->titleHandle);
+ DrawString(*peek->titleHandle);
+ HUnlock((Handle) peek->titleHandle);
+ }
+ break;
+ case kWindowMsgHitTest:
+ {
+ Point p;
+ p.v = param >> 16;
+ p.h = param & 0xFFFF;
+ if(PtInRgn(p, peek->strucRgn))
+ {
+ if(PtInRgn(p, peek->contRgn))
+ return wInContent;
+ else
+ return wInDrag;
+ }
+ else
+ return wNoHit;
+ }
+ break;
+ case kWindowMsgCalculateShape:
+ {
+ Rect r = window->portRect;
+ OffsetRect(&r, -window->portBits.bounds.left,
+ -window->portBits.bounds.top);
+ RectRgn(peek->contRgn, &r);
+ InsetRect(&r, -10, -10);
+ RectRgn(peek->strucRgn, &r);
+
+ HLock((Handle) peek->titleHandle);
+ short width = StringWidth(*peek->titleHandle);
+ HUnlock((Handle) peek->titleHandle);
+ width += 20;
+ if(width > r.right - r.left - 10)
+ width = r.right - r.left - 10;
+
+ RgnHandle rgn = NewRgn();
+ SetRectRgn(rgn, r.left, r.top-20, r.left + width, r.top);
+ UnionRgn(rgn, peek->strucRgn, peek->strucRgn);
+ DisposeRgn(rgn);
+ }
+
+ break;
+ case kWindowMsgInitialize:
+ break;
+ case kWindowMsgCleanUp:
+ break;
+ case kWindowMsgDrawGrowOutline:
+ break;
+ case kWindowMsgDrawGrowBox:
+ break;
+ }
+ return 0;
+}
diff --git a/Samples/WDEF/wdef.r b/Samples/WDEF/wdef.r
new file mode 100644
index 0000000000..1f2da567a3
--- /dev/null
+++ b/Samples/WDEF/wdef.r
@@ -0,0 +1,5 @@
+#include "Retro68.r"
+
+data 'WDEF' (129) {
+ $$read("WDEF")
+};
diff --git a/Samples/WDEF/wdefshell.c b/Samples/WDEF/wdefshell.c
new file mode 100644
index 0000000000..e077a34b08
--- /dev/null
+++ b/Samples/WDEF/wdefshell.c
@@ -0,0 +1,295 @@
+/*
+ Copyright 2017 Wolfgang Thaller.
+
+ This file is part of Retro68.
+
+ Retro68 is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Retro68 is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Retro68. If not, see .
+*/
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+ // in wdef.c
+extern pascal long MyWindowDefProc(short varCode, WindowRef window, short message, long param);
+
+static Rect initialWindowRect, nextWindowRect;
+
+enum
+{
+ kMenuApple = 128,
+ kMenuFile,
+ kMenuEdit
+};
+
+enum
+{
+ kItemAbout = 1,
+
+ kItemNewDoc = 1,
+ kItemNewRounded = 2,
+ kItemNewCustomFromStub = 3,
+ kItemNewCustomFromRes = 4,
+ kItemClose = 5,
+ kItemQuit = 7
+};
+
+void MakeNewWindow(ConstStr255Param title, short procID)
+{
+ if(nextWindowRect.bottom > qd.screenBits.bounds.bottom
+ || nextWindowRect.right > qd.screenBits.bounds.right)
+ {
+ nextWindowRect = initialWindowRect;
+ }
+
+ WindowRef w = NewWindow(NULL, &nextWindowRect, title, true, procID, (WindowPtr) -1, true, 0);
+
+ OffsetRect(&nextWindowRect, 15, 15);
+}
+
+void InitCustomWDEF()
+{
+/* The 10-byte code resource stub trick.
+ *
+ * The bytes in this resource are 68K machine code for
+ * move.l L1(pc), -(sp) | 2F3A 0004
+ * rts | 4E75
+ * L1: dc.l 0x00000000 | 0000 0000
+ *
+ * The application loads this resource and replaces the final four bytes
+ * with the address of MyWindowDefProc.
+ */
+
+ Handle h = GetResource('WDEF', 128);
+ HLock(h);
+ *(WindowDefProcPtr*)(*h + 6) = &MyWindowDefProc;
+
+ // By the way, this was the only part of this file relevant for dealing
+ // with custom WDEFs.
+}
+
+void ShowAboutBox()
+{
+ WindowRef w = GetNewWindow(128, NULL, (WindowPtr) - 1);
+ MacMoveWindow(w,
+ qd.screenBits.bounds.right/2 - w->portRect.right/2,
+ qd.screenBits.bounds.bottom/2 - w->portRect.bottom/2,
+ false);
+ ShowWindow(w);
+ SetPort(w);
+
+ Handle h = GetResource('TEXT', 128);
+ HLock(h);
+ Rect r = w->portRect;
+ InsetRect(&r, 10,10);
+ TETextBox(*h, GetHandleSize(h), &r, teJustLeft);
+
+ ReleaseResource(h);
+ while(!Button())
+ ;
+ while(Button())
+ ;
+ FlushEvents(everyEvent, 0);
+ DisposeWindow(w);
+}
+
+void UpdateMenus()
+{
+ MenuRef m = GetMenu(kMenuFile);
+ WindowRef w = FrontWindow();
+ if(w) // Close menu item: enabled if there is a window
+ EnableItem(m,kItemClose);
+ else
+ DisableItem(m,kItemClose);
+
+ m = GetMenu(kMenuEdit);
+ if(w && GetWindowKind(w) < 0)
+ {
+ // Desk accessory in front: Enable edit menu items
+ EnableItem(m,1);
+ EnableItem(m,3);
+ EnableItem(m,4);
+ EnableItem(m,5);
+ EnableItem(m,6);
+ }
+ else
+ {
+ // Application window or nothing in front, disable edit menu
+ DisableItem(m,1);
+ DisableItem(m,3);
+ DisableItem(m,4);
+ DisableItem(m,5);
+ DisableItem(m,6);
+ }
+
+}
+
+void DoMenuCommand(long menuCommand)
+{
+ Str255 str;
+ WindowRef w;
+ short menuID = menuCommand >> 16;
+ short menuItem = menuCommand & 0xFFFF;
+ if(menuID == kMenuApple)
+ {
+ if(menuItem == kItemAbout)
+ ShowAboutBox();
+ else
+ {
+ GetMenuItemText(GetMenu(128), menuItem, str);
+ OpenDeskAcc(str);
+ }
+ }
+ else if(menuID == kMenuFile)
+ {
+ switch(menuItem)
+ {
+ case kItemNewDoc:
+ GetIndString(str,128,1);
+ MakeNewWindow(str, documentProc); // plain document window
+ break;
+ case kItemNewRounded:
+ GetIndString(str,128,2);
+ MakeNewWindow(str, 16); // rounded document window
+ break;
+ case kItemNewCustomFromStub:
+ GetIndString(str,128,3);
+ MakeNewWindow(str, 128*16); // custom window, loaded via 10-byte stub
+ break;
+ case kItemNewCustomFromRes:
+ GetIndString(str,128,4);
+ MakeNewWindow(str, 129*16); // custom window, compiled as resource
+ break;
+
+ case kItemClose: // close
+ w = FrontWindow();
+ if(w)
+ {
+ if(GetWindowKind(w) < 0)
+ CloseDeskAcc(GetWindowKind(w));
+ else
+ DisposeWindow(FrontWindow());
+ }
+ break;
+
+ case kItemQuit:
+ ExitToShell();
+ break;
+ }
+ }
+ else if(menuID == kMenuEdit)
+ {
+ if(!SystemEdit(menuItem - 1))
+ {
+ // edit command not handled by desk accessory
+ }
+ }
+
+ HiliteMenu(0);
+}
+
+void DoUpdate(WindowRef w)
+{
+ SetPort(w);
+ BeginUpdate(w);
+
+ Rect r;
+ SetRect(&r, 20,20,120,120);
+ FrameOval(&r);
+
+ OffsetRect(&r, 32, 32);
+ FillRoundRect(&r, 16, 16, &qd.ltGray);
+ FrameRoundRect(&r, 16, 16);
+
+ OffsetRect(&r, 32, 32);
+ FillRect(&r, &qd.gray);
+ FrameRect(&r);
+
+ EndUpdate(w);
+}
+
+int main()
+{
+ InitGraf(&qd.thePort);
+ InitFonts();
+ InitWindows();
+ InitMenus();
+ TEInit();
+ InitDialogs(NULL);
+
+ SetMenuBar(GetNewMBar(128));
+ AppendResMenu(GetMenu(128), 'DRVR');
+ DrawMenuBar();
+
+ InitCustomWDEF();
+
+ InitCursor();
+
+ Rect r;
+ SetRect(&initialWindowRect,20,60,400,260);
+ nextWindowRect = initialWindowRect;
+
+ for(;;)
+ {
+ EventRecord e;
+ WindowRef win;
+
+ SystemTask();
+ if(GetNextEvent(everyEvent, &e))
+ {
+ switch(e.what)
+ {
+ case keyDown:
+ if(e.modifiers & cmdKey)
+ {
+ UpdateMenus();
+ DoMenuCommand(MenuKey(e.message & charCodeMask));
+ }
+ break;
+ case mouseDown:
+ switch(FindWindow(e.where, &win))
+ {
+ case inGoAway:
+ if(TrackGoAway(win, e.where))
+ DisposeWindow(win);
+ break;
+ case inDrag:
+ DragWindow(win, e.where, &qd.screenBits.bounds);
+ break;
+ case inMenuBar:
+ UpdateMenus();
+ DoMenuCommand( MenuSelect(e.where) );
+ break;
+ case inContent:
+ SelectWindow(win);
+ break;
+ case inSysWindow:
+ SystemClick(&e, win);
+ break;
+ }
+ break;
+ case updateEvt:
+ DoUpdate((WindowRef)e.message);
+ break;
+ }
+ }
+ }
+ return 0;
+}
diff --git a/Samples/WDEF/wdefshell.r b/Samples/WDEF/wdefshell.r
new file mode 100644
index 0000000000..33fbc31f41
--- /dev/null
+++ b/Samples/WDEF/wdefshell.r
@@ -0,0 +1,132 @@
+/*
+ Copyright 2017 Wolfgang Thaller.
+
+ This file is part of Retro68.
+
+ Retro68 is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Retro68 is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Retro68. If not, see .
+*/
+
+#include "Processes.r"
+#include "Menus.r"
+#include "Windows.r"
+#include "MacTypes.r"
+
+resource 'MENU' (128) {
+ 128, textMenuProc;
+ allEnabled, enabled;
+ apple;
+ {
+ "About WDEF Shell...", noIcon, noKey, noMark, plain;
+ "-", noIcon, noKey, noMark, plain;
+ }
+};
+
+resource 'MENU' (129) {
+ 129, textMenuProc;
+ allEnabled, enabled;
+ "File";
+ {
+ "New Document Window", noIcon, "N", noMark, plain;
+ "New Rounded Window", noIcon, "M", noMark, plain;
+ "New Custom Window (10-byte stub)", noIcon, "1", noMark, plain;
+ "New Custom Window (code resource)", noIcon, "2", noMark, plain;
+ "Close", noIcon, "W", noMark, plain;
+ "-", noIcon, noKey, noMark, plain;
+ "Quit", noIcon, "Q", noMark, plain;
+ }
+};
+
+resource 'MENU' (130) {
+ 130, textMenuProc;
+ 0, enabled;
+ "Edit";
+ {
+ "Undo", noIcon, "Z", noMark, plain;
+ "-", noIcon, noKey, noMark, plain;
+ "Cut", noIcon, "X", noMark, plain;
+ "Copy", noIcon, "C", noMark, plain;
+ "Paste", noIcon, "V", noMark, plain;
+ "Clear", noIcon, noKey, noMark, plain;
+ }
+};
+
+resource 'MBAR' (128) {
+ { 128, 129, 130 };
+};
+
+resource 'STR#' (128) {
+ {
+ "Standard Document Window",
+ "Rounded Document Window",
+ "Custom Window (10-byte stub)",
+ "Custom Window (code resource)";
+ }
+};
+
+data 'TEXT' (128) {
+ "About WDEF Shell\r\r"
+ "A Sample program that tries to show both how to write a basic classic Mac application, "
+ "and how to write a custom window definition procedure (WDEF) using Retro68.\r"
+ "Other code resources (CDEF, MDEF, LDEF, ...) aren't that much different.\r"
+ "\r\rWritten in 2018, so everything here has been obsolete for two decades."
+};
+
+resource 'WIND' (128) {
+ {0, 0, 220, 320}, altDBoxProc;
+ invisible;
+ noGoAway;
+ 0, "";
+ noAutoCenter;
+};
+
+/* The 10-byte code resource stub trick.
+ *
+ * The bytes in this resource are 68K machine code for
+ * move.l L1(pc), -(sp) | 2F3A 0004
+ * rts | 4E75
+ * L1: dc.l 0x00000000 | 0000 0000
+ *
+ * The application loads this resource and replaces the final four bytes
+ * with the address of the WDEF function in wdef.c, which is compiled as part
+ * of the application.
+ */
+data 'WDEF' (128) {
+ $"2F3A 0004 4E75 0000 0000"
+};
+
+resource 'SIZE' (-1) {
+ dontSaveScreen,
+ acceptSuspendResumeEvents,
+ enableOptionSwitch,
+ canBackground,
+ multiFinderAware,
+ backgroundAndForeground,
+ dontGetFrontClicks,
+ ignoreChildDiedEvents,
+ is32BitCompatible,
+ isHighLevelEventAware,
+ onlyLocalHLEvents,
+ notStationeryAware,
+ reserved,
+ reserved,
+ reserved,
+ reserved,
+#ifdef TARGET_API_MAC_CARBON
+ 500 * 1024, // Carbon apparently needs additional memory.
+ 500 * 1024
+#else
+ 100 * 1024,
+ 100 * 1024
+#endif
+};