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 +};