Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Initial check-in

git-svn-id: https://resknife.svn.sourceforge.net/svnroot/resknife/trunk/ResKnife@3 25122d23-1bc7-42f3-9251-2040de679093
  • Loading branch information...
commit 4471fd4ed7675d5a114c983362cf682561fe0a14 0 parents
nickshanks authored
Showing with 16,339 additions and 0 deletions.
  1. +56 −0 Bug List.text
  2. +1,132 −0 Carbon/Classes/Application.cpp
  3. +178 −0 Carbon/Classes/Application.h
  4. +1,710 −0 Carbon/Classes/Asynchronous.cpp
  5. +121 −0 Carbon/Classes/Asynchronous.h
  6. +634 −0 Carbon/Classes/DataBrowser.cpp
  7. +87 −0 Carbon/Classes/DataBrowser.h
  8. +57 −0 Carbon/Classes/EditorWindow.cpp
  9. +37 −0 Carbon/Classes/EditorWindow.h
  10. +89 −0 Carbon/Classes/Errors.cpp
  11. +42 −0 Carbon/Classes/Errors.h
  12. +1,799 −0 Carbon/Classes/FileWindow.cpp
  13. +179 −0 Carbon/Classes/FileWindow.h
  14. +860 −0 Carbon/Classes/Files.cpp
  15. +62 −0 Carbon/Classes/Files.h
  16. +186 −0 Carbon/Classes/HostCallbacks.cpp
  17. +245 −0 Carbon/Classes/HostCallbacks.h
  18. +265 −0 Carbon/Classes/InspectorWindow.cpp
  19. +26 −0 Carbon/Classes/InspectorWindow.h
  20. +73 −0 Carbon/Classes/PickerWindow.cpp
  21. +23 −0 Carbon/Classes/PickerWindow.h
  22. +95 −0 Carbon/Classes/PlugObject.cpp
  23. +97 −0 Carbon/Classes/PlugObject.h
  24. +91 −0 Carbon/Classes/PlugWindow.cpp
  25. +78 −0 Carbon/Classes/PlugWindow.h
  26. +100 −0 Carbon/Classes/ResourceObject.cpp
  27. +159 −0 Carbon/Classes/ResourceObject.h
  28. +220 −0 Carbon/Classes/Utility.cpp
  29. +73 −0 Carbon/Classes/Utility.h
  30. +75 −0 Carbon/Classes/WindowObject.cpp
  31. +86 −0 Carbon/Classes/WindowObject.h
  32. +16 −0 Carbon/Generic.h
  33. +310 −0 Carbon/ResKnife.h
  34. BIN  Carbon/ResKnife.lib
  35. +1 −0  Carbon/Resources/Carbon.r
  36. +295 −0 Carbon/Resources/ResKnife.nib/classes.nib
  37. +50 −0 Carbon/Resources/ResKnife.nib/info.nib
  38. +1,023 −0 Carbon/Resources/ResKnife.nib/objects.xib
  39. +1 −0  Carbon/Resources/ResKnife.r
  40. BIN  Carbon/Resources/ResKnife.rsrc
  41. +35 −0 Carbon/Transfer.h
  42. +14 −0 Cocoa/Classes/ApplicationDelegate.h
  43. +91 −0 Cocoa/Classes/ApplicationDelegate.m
  44. +13 −0 Cocoa/Classes/AttributesFormatter.h
  45. +65 −0 Cocoa/Classes/AttributesFormatter.m
  46. +21 −0 Cocoa/Classes/CreateResourceSheetController.h
  47. +32 −0 Cocoa/Classes/CreateResourceSheetController.m
  48. +6 −0 Cocoa/Classes/InfoWindow.h
  49. +15 −0 Cocoa/Classes/InfoWindow.m
  50. +34 −0 Cocoa/Classes/InfoWindowController.h
  51. +102 −0 Cocoa/Classes/InfoWindowController.m
  52. +13 −0 Cocoa/Classes/NameFormatter.h
  53. +41 −0 Cocoa/Classes/NameFormatter.m
  54. +13 −0 Cocoa/Classes/OutlineViewDelegate.h
  55. +20 −0 Cocoa/Classes/OutlineViewDelegate.m
  56. +22 −0 Cocoa/Classes/PrefsWindowController.h
  57. +93 −0 Cocoa/Classes/PrefsWindowController.m
  58. +44 −0 Cocoa/Classes/Resource.h
  59. +155 −0 Cocoa/Classes/Resource.m
  60. +23 −0 Cocoa/Classes/ResourceDataSource.h
  61. +91 −0 Cocoa/Classes/ResourceDataSource.m
  62. +21 −0 Cocoa/Classes/ResourceDocument.h
  63. +256 −0 Cocoa/Classes/ResourceDocument.m
  64. +9 −0 Cocoa/Classes/SizeFormatter.h
  65. +45 −0 Cocoa/Classes/SizeFormatter.m
  66. +4 −0 Cocoa/English.lproj/AboutPanel.nib/classes.nib
  67. +16 −0 Cocoa/English.lproj/AboutPanel.nib/info.nib
  68. BIN  Cocoa/English.lproj/AboutPanel.nib/objects.nib
  69. +29 −0 Cocoa/English.lproj/Application.nib/classes.nib
  70. +12 −0 Cocoa/English.lproj/Application.nib/info.nib
  71. BIN  Cocoa/English.lproj/Application.nib/objects.nib
  72. +6 −0 Cocoa/English.lproj/InfoPlist.strings
  73. +20 −0 Cocoa/English.lproj/InfoWindow.nib/classes.nib
  74. +18 −0 Cocoa/English.lproj/InfoWindow.nib/info.nib
  75. BIN  Cocoa/English.lproj/InfoWindow.nib/objects.nib
  76. +5 −0 Cocoa/English.lproj/Localizable.strings
  77. +13 −0 Cocoa/English.lproj/PrefsWindow.nib/classes.nib
  78. +18 −0 Cocoa/English.lproj/PrefsWindow.nib/info.nib
  79. BIN  Cocoa/English.lproj/PrefsWindow.nib/objects.nib
  80. +53 −0 Cocoa/English.lproj/ResourceDocument.nib/classes.nib
  81. +27 −0 Cocoa/English.lproj/ResourceDocument.nib/info.nib
  82. BIN  Cocoa/English.lproj/ResourceDocument.nib/objects.nib
  83. BIN  Cocoa/Resources/Icon file.icns
  84. BIN  Cocoa/Resources/ResKnife.icns
  85. BIN  Cocoa/Resources/Resource file.icns
  86. +6 −0 Cocoa/Resources/defaults.plist
  87. +6 −0 Cocoa/main.c
  88. +998 −0 Hex Editor/Classes/Events.cpp
  89. +1 −0  Hex Editor/Classes/Events.h
  90. +409 −0 Hex Editor/Classes/HexWindow.cpp
  91. +1 −0  Hex Editor/Classes/HexWindow.h
  92. +1 −0  Hex Editor/Classes/Initalisation.cpp
  93. +1 −0  Hex Editor/Classes/Initalisation.h
  94. +233 −0 Hex Editor/Classes/Utility.cpp
  95. +31 −0 Hex Editor/Classes/Utility.h
  96. +149 −0 Hex Editor/Hex Editor.h
  97. +1 −0  PICT Editor/Classes/Initalisation.cpp
  98. +1 −0  PICT Editor/Classes/Initalisation.h
  99. 0  PICT Editor/Classes/Parser.h
  100. +7 −0 PICT Editor/Classes/PictWindow.cpp
  101. +1 −0  PICT Editor/PICT Editor.h
  102. +1 −0  Prefix Files/Carbon Prefix.h
  103. +1 −0  Prefix Files/Classic Prefix.h
  104. BIN  ResKnife.mcp
  105. +150 −0 ResKnife.pbproj/nicholas.pbxuser
  106. +2,133 −0 ResKnife.pbproj/project.pbxproj
  107. +1 −0  Template Editor/Classes/Initalisation.cpp
  108. +1 −0  Template Editor/Classes/Initalisation.h
  109. +1 −0  Template Editor/Classes/TemplateWindow.cpp
  110. +48 −0 Template Editor/Classes/TemplateWindow.h
  111. +1 −0  Template Editor/Template Editor.h
  112. +30 −0 Template Editor/Template Editor.r
56 Bug List.text
@@ -0,0 +1,56 @@
+ResKnife (Carbon & Classic):
+* quit doesn't check for files to save (unverified, seems to check front window on 9.1 - don't know about others)
+* files without a resource fork (inc. DF-based resource files) crash upon opening
+* when saving a file, the temporary file cannot be deleted because it is open. I cannot find where I open it without closing it again.
+* there's a crash on quit when there's an unsaved file open
+ temporary file handling is not very good. make it cooler.
+ should pass plug-ins a copy of the data handle, not the real thing
+ window header text non-existant
+ Revert Resource menu item not plugged in
+ should call memset() not BlockZero() on pre 8.5 systems (and include strings.h)
+
+ResKnife (Carbon Only):
+ DataBrowser still doesn't draw the line at the top
+ choosing debugging menu items doesn't checkmark them
+ cannot drag items into/out of the data browser
+
+ResKnife (Classic Only):
+ no column sorting
+ no GWorld support or clipRgn managment
+ headers don't toggle (Appearance)
+ no headers present (non-Appearance)
+
+All Editors
+* only have carbon support
+
+Hex Editor:
+ closing winodow sometimes (rarely) causes crashes. cause unknown
+
+Template Editor:
+* some fields are not saved (elaborate)
+* does not save repeating templates (I really need help with this)
+* cannot handle Hxxx, P0xx, Cxxx
+ multi-line strings do not cause the text box to expand (anyone know how to make this work?)
+
+PICT Editor:
+ Window doesn't have minimum size
+ Window is not resizable, has no scrollbars either
+
+
+* denotes important bug (e.g. reproducable crasher, loss of data, et cetera)
+I am currently working to eliminate these asterisked bugs, the others I'm not so bothered about yet.
+I shall soon begin implementing an event mechanism for the editors which doesn't use CarbonEvents, allowing me to release the Classic build with some editors. (Fear not all you System 7.1-ers!)
+
+please send bug reports (or better yet, contribute code) to resknife@nickshanks.com
+
+One-time only bugs:
+choosing save after editing a data fork caused only 2 of 14 resources to be saved.
+clicking in the hex window does weird things to the selection. No selection was present, and the insertion point was not visible either. Typing subsequently deleted everything after where I typed, but no character appeared
+
+
+
+ResKnife (Cocoa):
+* Setting a type which is longer than four bytes on one resource will cause all resources below it to not be saved.
+ - (should require user to enter no more or less than four bytes, use formatter objects' isPartialStringValid: method)
+* create new resource sheet not yet implemented
+* no editors
1,132 Carbon/Classes/Application.cpp
@@ -0,0 +1,1132 @@
+#include "Application.h"
+#include "Asynchronous.h" // for initing, idling, and disposing
+#include "Errors.h"
+#include "Files.h" // for open & save etc.
+#include "FileWindow.h" // no particuar reason
+#include "ResourceObject.h"
+#include "Utility.h"
+
+// set up prefs and globals
+globals g;
+prefs p;
+
+/*** MAIN ***/
+int main( int argc, char* argv[] )
+{
+ #pragma unused( argc, argv )
+
+ // get system version
+ OSStatus error = Gestalt( gestaltSystemVersion, &g.systemVersion ); // this loads HIToolbox.framework on OS X
+ if( error ) return error;
+
+ // initalise application
+ error = InitToolbox();
+ if( error ) return error;
+ error = InitMenubar();
+ if( error ) return error;
+ error = InitAppleEvents();
+ if( error ) return error;
+ error = InitCarbonEvents();
+ if( error ) return error;
+ error = InitGlobals();
+ if( error ) return error;
+ InitCursor();
+
+ // check system version is at least 7.1
+ if( g.systemVersion < kMacOS71 )
+ {
+ DisplayError( kStringOSNotGoodEnough, kExplanationOSNotGoodEnough );
+ QuitResKnife();
+ }
+
+ // check carbon version is at least 1.1
+ error = Gestalt( gestaltCarbonVersion, &g.carbonVersion );
+ if( g.carbonVersion < kCarbonLib11 || error )
+ {
+ DisplayError( kStringMinimumCarbonLib, kExplanationMinimumCarbonLib );
+ QuitResKnife();
+ }
+ else if( g.carbonVersion < kCarbonLib131 )
+ {
+ DisplayError( kStringRecommendedCarbonLib, kExplanationRecommendedCarbonLib );
+ }
+
+#if __profile__
+ error = ProfilerInit( collectDetailed, bestTimeBase, 400, 40 );
+ if( error ) DebugStr( "\pProfiler initalisation failed" ); // profiler failed
+#endif
+
+#if TARGET_API_MAC_CARBON
+ // run event loop
+ RunApplicationEventLoop();
+#else
+ EventRecord theEvent;
+ while( !g.quitting )
+ {
+ WaitNextEvent( everyEvent, &theEvent, 20, null );
+ if( IsDialogEvent( &theEvent ) )
+ ParseDialogEvents( null, &theEvent, null );
+ else ParseEvents( &theEvent );
+ }
+ QuitResKnife();
+#endif
+
+#if __profile__
+ ProfilerDump( "\pResKnife profile" );
+ ProfilerTerm();
+#endif
+
+ return error;
+}
+
+/*** INIT TOOLBOX ***/
+OSStatus InitToolbox( void )
+{
+#if !TARGET_API_MAC_CARBON
+ InitGraf( &qd.thePort );
+ InitFonts();
+ FlushEvents( everyEvent, 0 );
+ InitWindows();
+ InitMenus();
+ TEInit();
+ InitDialogs( 0L );
+#endif
+ return noErr;
+}
+
+/*** INITALIZE MENUBAR ***/
+OSStatus InitMenubar( void )
+{
+ OSStatus error = noErr;
+
+#if USE_NIBS
+ IBNibRef nibRef = null;
+
+ // create a nib reference (only searches the application bundle)
+ error = CreateNibReference( CFSTR("ResKnife"), &nibRef );
+ if( error != noErr )
+ {
+ DisplayError( "\pThe nib file reference could not be obtained." );
+ return error;
+ }
+
+ // get menu bar
+ error = SetMenuBarFromNib( nibRef, CFSTR("Menubar") );
+ if( error != noErr )
+ {
+ DisplayError( "\pMenus could not be obtained from nib file." );
+ return error;
+ }
+
+ // dispose of nib ref
+ DisposeNibReference( nibRef );
+
+#else /* ! USE_NIBS */
+
+ Handle menuList = GetNewMBar( kClassicMenuBar );
+ SetMenuBar( menuList );
+ ReleaseResource( menuList );
+
+ // delete quit and prefs on OS X
+ long result;
+ error = Gestalt( gestaltMenuMgrAttr, &result );
+ if( !error && (result & gestaltMenuMgrAquaLayoutMask) )
+ {
+ MenuRef fileMenu = GetMenuRef( kFileMenu );
+ MenuRef editMenu = GetMenuRef( kEditMenu );
+ DeleteMenuItem( fileMenu, kFileMenuQuitItem );
+ DeleteMenuItem( fileMenu, kFileMenuQuitItem -1 );
+ DeleteMenuItem( editMenu, kEditMenuPreferencesItem );
+ DeleteMenuItem( editMenu, kEditMenuPreferencesItem -1 );
+ }
+
+ // set delete item character to the delete glyph
+ MenuRef editMenu = GetMenuRef( kEditMenu );
+ SetMenuItemKeyGlyph( editMenu, kEditMenuClearItem, kMenuDeleteLeftGlyph );
+
+#if TARGET_API_MAC_CARBON
+ MenuRef windowMenu;
+ CreateStandardWindowMenu( 0, &windowMenu );
+ InsertMenu( windowMenu, kWindowMenu );
+#else
+ AppendResMenu( GetMenuRef( kAppleMenu ), 'DRVR' );
+#endif /* TARGET_CARBON */
+ DrawMenuBar();
+
+#endif /* USE_NIBS */
+
+ return error;
+}
+
+/*** INIT APPLE EVENTS ***/
+OSStatus InitAppleEvents( void )
+{
+ AEEventHandlerUPP appleEventParser = NewAEEventHandlerUPP( ParseAppleEvents );
+ AEInstallEventHandler( kCoreEventClass, kAEOpenApplication, appleEventParser, 0, false );
+ AEInstallEventHandler( kCoreEventClass, kAEReopenApplication, appleEventParser, 0, false );
+ AEInstallEventHandler( kCoreEventClass, kAEOpenDocuments, appleEventParser, 0, false );
+ AEInstallEventHandler( kCoreEventClass, kAEPrintDocuments, appleEventParser, 0, false );
+ AEInstallEventHandler( kCoreEventClass, kAEQuitApplication, appleEventParser, 0, false );
+ return noErr;
+}
+
+/*** INIT CARBON EVENTS ***/
+OSStatus InitCarbonEvents( void )
+{
+#if TARGET_API_MAC_CARBON
+ EventHandlerUPP handler = null;
+ EventHandlerRef ref = null;
+ EventTypeSpec update = { kEventClassMenu, kEventMenuEnableItems };
+ EventTypeSpec process = { kEventClassCommand, kEventCommandProcess };
+
+ // install menu adjust handler
+ handler = NewEventHandlerUPP( CarbonEventUpdateMenus );
+ InstallApplicationEventHandler( handler, 1, &update, null, &ref );
+
+ // install menu selection handler
+ handler = NewEventHandlerUPP( CarbonEventParseMenuSelection );
+ InstallApplicationEventHandler( handler, 1, &process, null, &ref );
+
+ // install default idle timer Ñ 200 millisecond interval - bug: this should be in a seperate thread and the cursor blink time
+ EventLoopTimerUPP timerUPP = NewEventLoopTimerUPP( DefaultIdleTimer );
+ OSStatus error = InstallEventLoopTimer( GetMainEventLoop(), kEventDurationNoWait, kEventDurationMillisecond * 200, timerUPP, null, &g.idleTimer );
+ return error;
+#else
+ return noErr;
+#endif
+}
+
+/*** INITALIZE GLOBALS ***/
+OSStatus InitGlobals( void )
+{
+ OSStatus error = noErr;
+
+ // general app globals
+ g.quitting = false;
+ g.cancelQuit = false;
+ g.frontApp = true;
+ g.appResFile = CurResFile();
+ g.asyncSound = !(Boolean) SHInitSoundHelper( &g.callSH, kSHDefChannels );
+ g.emergencyMemory = NewHandleClear( kEmergencyMemory );
+
+ // files
+ g.tempCount = 0;
+
+ // debugging
+ g.debug = false;
+ g.surpressErrors = false;
+ g.useAppleEvents = true;
+ g.useSheets = (g.carbonVersion >= kCarbonLib11)? true:false;
+
+ // prefs dialog
+ g.prefsDialog = null;
+ p.warnOnDelete = true;
+
+ // colours
+ SetColour( &g.white, 0xFFFF, 0xFFFF, 0xFFFF );
+ SetColour( &g.bgColour, 0xEEEE, 0xEEEE, 0xEEEE );
+ SetColour( &g.sortColour, 0xDDDD, 0xDDDD, 0xDDDD );
+ SetColour( &g.bevelColour, 0xAAAA, 0xAAAA, 0xAAAA );
+ SetColour( &g.textColour, 0x7777, 0x7777, 0x7777 );
+ SetColour( &g.frameColour, 0x5555, 0x5555, 0x5555 );
+ SetColour( &g.black, 0x0000, 0x0000, 0x0000 );
+
+#if TARGET_API_MAC_CARBON
+ // window manager
+ g.windowMgrAvailable = true;
+ g.extendedWindowAttr = true;
+
+ // drag manager
+ g.dragAvailable = true;
+ g.translucentDrag = true;
+
+ // appearance manager
+ g.appearanceAvailable = true;
+ g.useAppearance = g.appearanceAvailable; // assume if user has Appearence, s/he wants to use it
+ if( g.useAppearance ) RegisterAppearanceClient(); // register such with the OS
+
+ // nav services
+ g.navAvailable = true;
+ g.useNavServices = g.navAvailable; // assume if user has NavServices, s/he wants to use them
+ if( g.navAvailable ) NavLoad(); // preload for efficiency - ignored on OS X (always loaded)
+#else
+ // check for drag manager presence/attributes
+ SInt32 result = null;
+ error = Gestalt( gestaltDragMgrAttr, &result );
+ if( !error ) {
+ g.dragAvailable = (Boolean) (result & (1 << gestaltDragMgrPresent));
+ g.translucentDrag = (Boolean) (result & (1 << gestaltDragMgrHasImageSupport)); }
+ else {
+ g.dragAvailable = false;
+ g.translucentDrag = false; }
+
+ // check appearance availablilty
+ result = null;
+ error = Gestalt( gestaltAppearanceAttr, &result );
+ if( !error ) {
+ g.appearanceAvailable = (Boolean) (result & (1 << gestaltAppearanceExists));
+ g.useAppearance = g.appearanceAvailable; } // assume if user has Appearence, s/he wants to use it
+ else {
+ g.appearanceAvailable = false;
+ g.useAppearance = false; }
+ if( g.useAppearance ) RegisterAppearanceClient(); // register such with the OS
+
+ // check nav services availablilty
+ g.navAvailable = (Boolean) NavServicesAvailable();
+ g.useNavServices = g.navAvailable; // assume if user has NavServices, s/he wants to use them
+ if( g.navAvailable ) NavLoad(); // preload for efficiency
+
+ // check for MacOS 8.5's window manager (also in CarbonLib 1.0 - backported to 8.1)
+ result = null;
+ error = Gestalt( gestaltWindowMgrAttr, &result );
+ if( !error ) {
+ g.windowMgrAvailable = (Boolean) (result & (1 << gestaltWindowMgrPresentBit));
+ g.extendedWindowAttr = (Boolean) (result & (1 << gestaltExtendedWindowAttributes)); }
+ else {
+ g.windowMgrAvailable = false;
+ g.extendedWindowAttr = false; }
+
+ UpdateMenus( null );
+#endif
+ return error;
+}
+
+#if !TARGET_API_MAC_CARBON
+
+/*** PARSE EVENTS ***/
+OSStatus ParseEvents( EventRecord *event )
+{
+ OSStatus error = eventNotHandledErr;
+ switch( event->what )
+ {
+ case nullEvent:
+ IdleEvent();
+ break;
+ case mouseDown:
+ error = MouseDownEventOccoured( event );
+ break;
+ case mouseUp:
+ error = MouseUpEventOccoured( event );
+ break;
+ case keyDown:
+ error = KeyDownEventOccoured( event );
+ break;
+ case autoKey:
+ error = KeyRepeatEventOccoured( event );
+ break;
+ case keyUp:
+ error = KeyUpEventOccoured( event );
+ break;
+ case updateEvt:
+ error = UpdateEventOccoured( event );
+ break;
+ case activateEvt:
+ error = ActivateEventOccoured( event );
+ break;
+ case osEvt:
+ error = ParseOSEvents( event );
+ break;
+ case kHighLevelEvent:
+ error = AEProcessAppleEvent( event );
+ break;
+ }
+ return error;
+}
+
+/*** PARSE DIALOG EVENTS ***/
+pascal Boolean ParseDialogEvents( DialogPtr dialog, EventRecord *event, DialogItemIndex *itemHit )
+{
+ #pragma unused( dialog, event, itemHit )
+/* OSStatus error = eventNotHandledErr;
+ if( dialog == null && itemHit == null );
+*/ return false;
+}
+
+/*** PARSE OS EVENTS ***/
+OSStatus ParseOSEvents( EventRecord *event )
+{
+ #pragma unused( event )
+ OSStatus error = eventNotHandledErr;
+ SInt8 eventType = event->message >> 24; // high byte of message field
+ if( eventType & mouseMovedMessage )
+ {
+ // mouse moved event
+ }
+ else if( eventType & suspendResumeMessage ) // suspend/resume event
+ {
+ g.frontApp = ( event->message & resumeFlag ); // true on resume
+ if( FrontWindow() ) // only de/activate front window (if present)
+ {
+ WindowObjectPtr winObj = (WindowObjectPtr) GetWindowRefCon( FrontWindow() );
+ error = winObj->Activate( g.frontApp );
+ }
+ if( event->message & convertClipboardFlag )
+ {
+ // convert clipboard to private scrap
+ }
+ }
+ else error = paramErr;
+ return error;
+}
+
+#endif
+
+/*** PARSE APPLE EVENTS ***/
+pascal OSErr ParseAppleEvents( const AppleEvent *event, AppleEvent *reply, SInt32 refCon )
+{
+ #pragma unused( reply, refCon )
+
+ OSErr error;
+ Size actualSize;
+ DescType actualType;
+ DescType eventClass, eventID;
+
+ error = AEGetAttributePtr( (AppleEvent *) event, keyEventClassAttr, typeType, &actualType, (Ptr) &eventClass, sizeof(eventClass), &actualSize );
+ if( error ) return errAEEventNotHandled;
+
+ error = AEGetAttributePtr( (AppleEvent *) event, keyEventIDAttr, typeType, &actualType, (Ptr) &eventID, sizeof(eventID), &actualSize );
+ if( error ) return errAEEventNotHandled;
+
+ switch( eventClass )
+ {
+ case kCoreEventClass:
+ switch( eventID )
+ {
+ case kAEOpenApplication: // sent when app opened directly (ie not file opened)
+#if TARGET_API_MAC_CARBON
+ DisplayOpenDialog();
+#else
+ if( g.useNavServices ) DisplayOpenDialog();
+ else DisplayStandardFileOpenDialog();
+#endif
+ break;
+
+ case kAEReopenApplication: // sent when app is double-clicked on, but is already open
+ if( FrontWindow() == null )
+ {
+ AEDescList list = {};
+#if TARGET_API_MAC_CARBON
+ AppleEventSendSelf( kCoreEventClass, kAEOpenApplication, list );
+#else
+ if( g.useAppleEvents ) AppleEventSendSelf( kCoreEventClass, kAEOpenApplication, list );
+ else if( g.useNavServices ) DisplayOpenDialog();
+ else DisplayStandardFileOpenDialog();
+#endif
+ }
+ break;
+
+ case kAEOpenDocuments: // sent when file is double-clicked on in finder,
+ AppleEventOpen( event ); // or open is chosen in the file menu and g.useAppleEvents is true
+ break;
+
+ case kAEPrintDocuments: // sent when document is dragged onto printer
+ AppleEventPrint( event );
+ break;
+
+ case kAEQuitApplication: // sent from many locations (eg after restart command)
+ QuitResKnife();
+ break;
+ }
+ break;
+
+/* case kAECoreSuite: // i'm not even registering for these yet
+ switch( eventID )
+ {
+ case kAECut:
+ case kAECopy:
+ case kAEPaste:
+ case kAEDelete:
+ DisplayErrorDialog( "\pSorry, but cut, copy, paste and clear via Apple Events arn't yet supported." );
+ error = errAEEventNotHandled;
+ break;
+ }
+ break;
+*/ }
+ return error;
+}
+
+ /******************/
+ /* EVENT HANDLING */
+/******************/
+
+#if !TARGET_API_MAC_CARBON
+
+/*** MOUSE DOWN EVENT OCCOURED ***/
+OSStatus MouseDownEventOccoured( EventRecord *event )
+{
+ // get the window
+ OSStatus error = eventNotHandledErr;
+ WindowRef window;
+ SInt16 windowPart = FindWindow( event->where, &window );
+ WindowObjectPtr winObj = (WindowObjectPtr) GetWindowRefCon( window );
+
+ // find out where the click occoured
+ if( !windowPart ) return error;
+ else switch( windowPart )
+ {
+ case inMenuBar:
+ error = UpdateMenus( FrontWindow() ); // error ignored at the moment
+ UInt32 menuChoice = MenuSelect( event->where );
+ UInt16 menu = HiWord( menuChoice );
+ UInt16 item = LoWord( menuChoice );
+ error = ParseMenuSelection( menu, item ); // error ignored at the moment
+ HiliteMenu( 0 );
+ break;
+
+ case inSysWindow:
+ SystemClick( event, window );
+ break;
+
+ case inContent:
+ SelectWindow( window );
+ winObj->Click( event->where, event->modifiers );
+ break;
+
+ case inDrag:
+ DragWindow( window, event->where, &qdb );
+ winObj->BoundsChanged( null );
+ break;
+
+ case inGrow:
+ Rect bounds; // minimum and maximum bounds of window
+ SetRect( &bounds, 128, 128, 1024, 1024 );
+ SInt32 result = GrowWindow( window, event->where, &bounds );
+ if( result )
+ {
+ SInt16 newWidth = LoWord( result );
+ SInt16 newHeight = HiWord( result );
+ SizeWindow( window, newWidth, newHeight, false );
+ winObj->BoundsChanged( null );
+ }
+ break;
+
+ case inGoAway:
+ if( TrackGoAway( window, event->where ) )
+ winObj->Close();
+ break;
+
+ case inZoomIn:
+ case inZoomOut:
+ if( TrackBox( window, event->where, windowPart ) )
+ {
+ ZoomWindow( window, windowPart, window == FrontWindow() ); // I think the last param *might* need to be "g.frontApp"
+ winObj->BoundsChanged( null );
+ }
+ break;
+
+ case inCollapseBox:
+ case inProxyIcon:
+ break;
+ }
+ return error;
+}
+
+/*** MOUSE UP EVENT OCCOURED ***/
+OSStatus MouseUpEventOccoured( EventRecord *event )
+{
+ #pragma unused( event )
+ OSStatus error = eventNotHandledErr;
+ return error;
+}
+
+/*** KEY DOWN EVENT OCCOURED ***/
+OSStatus KeyDownEventOccoured( EventRecord *event )
+{
+ OSStatus error = eventNotHandledErr;
+ char key = (char)( event->message & charCodeMask ); // get the key pressed
+ if( event->modifiers & cmdKey ) // was it a menu shortcut?
+ {
+ UpdateMenus( FrontWindow() );
+ UInt32 menuChoice = MenuKey( key );
+ UInt16 menu = HiWord( menuChoice );
+ UInt16 item = LoWord( menuChoice );
+ if( menu && item )
+ error = ParseMenuSelection( menu, item );
+ }
+ return error;
+}
+
+/*** KEY REPEAT EVENT OCCOURED ***/
+OSStatus KeyRepeatEventOccoured( EventRecord *event )
+{
+ OSStatus error = KeyDownEventOccoured( event );
+ return error;
+}
+
+/*** KEY UP EVENT OCCOURED ***/
+OSStatus KeyUpEventOccoured( EventRecord *event )
+{
+ #pragma unused( event )
+ OSStatus error = eventNotHandledErr;
+ return error;
+}
+
+/*** UPDATE EVENT OCCOURED ***/
+OSStatus UpdateEventOccoured( EventRecord *event )
+{
+ OSStatus error = eventNotHandledErr;
+ GrafPtr oldPort;
+ WindowRef window = (WindowRef) event->message;
+
+ // send update events to window
+ GetPort( &oldPort );
+ SetPortWindowPort( window );
+ BeginUpdate( window ); // this sets up the visRgn
+ {
+ RgnHandle updateRgn = NewRgn();
+ WindowObjectPtr winObj = (WindowObjectPtr) GetWindowRefCon( window );
+ GetPortVisibleRegion( GetWindowPort( window ), updateRgn );
+ if( winObj ) error = winObj->Update( updateRgn );
+ DisposeRgn( updateRgn );
+ }
+ EndUpdate( window );
+ SetPort( oldPort );
+ return error;
+}
+
+/*** ACTIVATE EVENT OCCOURED ***/
+OSStatus ActivateEventOccoured( EventRecord *event )
+{
+ OSStatus error = eventNotHandledErr;
+ WindowObjectPtr winObj = (WindowObjectPtr) GetWindowRefCon( (WindowRef) event->message );
+ if( winObj ) error = winObj->Activate( (Boolean) (event->modifiers & activeFlag) );
+ return error;
+}
+
+/*** IDLE EVENT ***/
+OSStatus IdleEvent( void )
+{
+ // call sound idle routine
+ if( g.asyncSound ) SHIdle();
+
+ // compact all memory
+/* SInt32 total, contig;
+ PurgeMem( kEmergencyMemory );
+ CompactMem( kEmergencyMemory );
+ PurgeSpace( &total, &contig );
+
+ // deal with emergency memory
+ if( total < kMinimumFreeMemory && g.emergencyMemory )
+ {
+ DisposeHandle( g.emergencyMemory ); // release emergence memory
+ DisplayError( "\pMemory is running low, please close some windows." );
+ }
+ else if( !g.emergencyMemory && contig > kEmergencyMemory ) // try to recover handle if possible
+ g.emergencyMemory = NewHandleClear( kEmergencyMemory );
+*/ return noErr;
+}
+
+#endif
+
+/*** QUIT RES KNIFE ***/
+void QuitResKnife( void )
+{
+ // save all open files
+ WindowRef window = FrontNonFloatingWindow(), nextWindow;
+ while( window )
+ {
+ nextWindow = GetNextWindow( window );
+ SInt32 kind = GetWindowKind( window );
+ if( kind == kFileWindowKind )
+ {
+#if TARGET_API_MAC_CARBON
+ EventRef event;
+ CreateEvent( null, kEventClassWindow, kEventWindowClose, kEventDurationNoWait, kEventAttributeNone, &event );
+ SendEventToWindow( event, window );
+ ReleaseEvent( event );
+#else
+ // bug: this is totally the wrong thing to do here, but will have to do for now
+ DisposeWindow( window ); // bug: windows don't close when sent a WindowClose event!
+#endif
+ }
+ window = nextWindow;
+ }
+
+ if( !g.cancelQuit )
+ {
+ if( g.asyncSound ) SHKillSoundHelper();
+ if( g.navAvailable ) NavUnload();
+#if TARGET_API_MAC_CARBON
+ QuitApplicationEventLoop();
+#else
+ g.quitting = true;
+#endif
+ }
+}
+
+ /*******************/
+ /* MENU SELECTIONS */
+/*******************/
+
+#if TARGET_API_MAC_CARBON
+
+/*** CARBON EVENT UPDATE MENUS ***/
+pascal OSStatus CarbonEventUpdateMenus( EventHandlerCallRef callRef, EventRef event, void *userData )
+{
+ #pragma unused( callRef, event, userData )
+ OSStatus error = eventNotHandledErr;
+ Boolean fileOpen = (Boolean) FrontNonFloatingWindow();
+
+ // application menu (passing null causes all menus to be searched)
+ EnableCommand( null, kMenuCommandAbout, true );
+ EnableCommand( null, kHICommandPreferences, true );
+
+ // file menu
+ EnableCommand( null, kMenuCommandNewFile, true );
+ EnableCommand( null, kMenuCommandOpenFile, true );
+ EnableCommand( null, kMenuCommandCloseWindow, fileOpen );
+ EnableCommand( null, kMenuCommandCloseFile, fileOpen );
+ EnableCommand( null, kMenuCommandSaveFile, fileOpen ); // bug: shoud be disabled if file is unmodified
+ EnableCommand( null, kMenuCommandSaveFileAs, fileOpen );
+ EnableCommand( null, kMenuCommandRevertFile, fileOpen );
+ EnableCommand( null, kMenuCommandPageSetup, fileOpen );
+ EnableCommand( null, kMenuCommandPrint, fileOpen );
+
+/* if( fileOpen )
+ {
+ // edit window
+ EnableCommand( null, kHICommandUndo, false );
+ EnableCommand( null, kHICommandRedo, false );
+ EnableCommand( null, kHICommandCut, false );
+ EnableCommand( null, kHICommandCopy, false );
+ EnableCommand( null, kHICommandPaste, false );
+ EnableCommand( null, kHICommandClear, false );
+ EnableCommand( null, kHICommandSelectAll, false );
+ EnableCommand( null, kMenuCommandFind, false );
+ EnableCommand( null, kMenuCommandFindAgain, false );
+
+ // resource menu
+ EnableCommand( null, kMenuCommandNewResource, false );
+ EnableCommand( null, kMenuCommandOpenHex, false );
+ EnableCommand( null, kMenuCommandOpenDefault, false );
+ EnableCommand( null, kMenuCommandOpenTemplate, false );
+ EnableCommand( null, kMenuCommandOpenSpecific, false );
+ EnableCommand( null, kMenuCommandRevertResource, false );
+ EnableCommand( null, kMenuCommandPlaySound, false );
+ }
+*/
+ // debug menu
+ EnableCommand( null, kMenuCommandDebug, true );
+ EnableCommand( null, kMenuCommandAppleEvents, false );
+ EnableCommand( null, kMenuCommandAppearance, false );
+ EnableCommand( null, kMenuCommandNavServices, false );
+ EnableCommand( null, kMenuCommandSheets, g.carbonVersion >= kCarbonLib11 );
+ SetMenuCommandMark( null, kMenuCommandDebug, g.debug? kMenuCheckmarkGlyph : kMenuNullGlyph );
+ SetMenuCommandMark( null, kMenuCommandSurpressErrors, g.surpressErrors? kMenuCheckmarkGlyph : kMenuNullGlyph );
+ SetMenuCommandMark( null, kMenuCommandAppleEvents, g.useAppleEvents? kMenuCheckmarkGlyph : kMenuNullGlyph ); // these three should always be true in Carbon
+ SetMenuCommandMark( null, kMenuCommandAppearance, g.useAppearance? kMenuCheckmarkGlyph : kMenuNullGlyph );
+ SetMenuCommandMark( null, kMenuCommandNavServices, g.useNavServices? kMenuCheckmarkGlyph : kMenuNullGlyph );
+ SetMenuCommandMark( null, kMenuCommandSheets, g.useSheets? kMenuCheckmarkGlyph : kMenuNullGlyph );
+
+ return error;
+}
+
+/*** CARBON EVENT PARSE MENU SELECTION ***/
+pascal OSStatus CarbonEventParseMenuSelection( EventHandlerCallRef callRef, EventRef event, void *userData )
+{
+ #pragma unused( callRef, userData )
+
+ // get menu command
+ HICommand menuCommand;
+ OSStatus error = GetEventParameter( event, kEventParamDirectObject, typeHICommand, null, sizeof(HICommand), null, &menuCommand );
+ if( error ) return eventNotHandledErr;
+
+ switch( menuCommand.commandID )
+ {
+ // application menu
+ case kMenuCommandAbout:
+ ShowAboutBox();
+ break;
+
+ case kHICommandPreferences:
+ ShowPrefsWindow();
+ break;
+
+ // file menu
+ case kMenuCommandNewFile:
+ new FileWindow;
+ break;
+
+ case kMenuCommandOpenFile:
+ if( g.useSheets ) error = DisplayModelessGetFileDialog();
+ else error = DisplayOpenDialog();
+ break;
+
+ // debug menu
+ case kMenuCommandDebug:
+ g.debug = !g.debug;
+ break;
+ case kMenuCommandSurpressErrors:
+ g.surpressErrors = !g.surpressErrors;
+ break;
+ case kMenuCommandAppleEvents:
+ g.useAppleEvents = !g.useAppleEvents;
+ break;
+ case kMenuCommandAppearance:
+ g.useAppearance = !g.useAppearance;
+ break;
+ case kMenuCommandNavServices:
+ g.useNavServices = !g.useNavServices;
+ break;
+ case kMenuCommandSheets:
+ g.useSheets = !g.useSheets;
+ break;
+
+ default:
+ return eventNotHandledErr; // pass all other events
+ }
+
+ return error; // event handled here, so terminate.
+}
+
+/*** DEFAULT IDLE TIMER ***/
+pascal void DefaultIdleTimer( EventLoopTimerRef timer, void *data )
+{
+ #pragma unused( timer, data )
+
+ // idle controls (allow cursor blinking)
+ IdleControls( GetUserFocusWindow() ); // bug: apple should have the control install it's own timer
+
+ // call sound idle routine
+ if( g.asyncSound && g.callSH ) SHIdle();
+}
+
+#else
+
+/*** UPDATE MENUS ***/
+OSStatus UpdateMenus( WindowRef window )
+{
+ #pragma unused( window )
+ OSStatus error = noErr;
+
+ // disable/checkmark items in the debug menu
+ MenuRef debugMenu = GetMenuRef( kDebugMenu );
+ MenuItemEnable( debugMenu, kDebugMenuAppearanceItem, g.appearanceAvailable );
+ MenuItemEnable( debugMenu, kDebugMenuNavServicesItem, g.navAvailable );
+ MenuItemEnable( debugMenu, kDebugMenuSheetsItem, false );
+ CheckMenuItem( debugMenu, kDebugMenuDebugItem, g.debug );
+ CheckMenuItem( debugMenu, kDebugMenuSurpressErrorsItem, g.surpressErrors );
+ CheckMenuItem( debugMenu, kDebugMenuAppleEventsItem, g.useAppleEvents );
+ CheckMenuItem( debugMenu, kDebugMenuAppearanceItem, g.useAppearance );
+ CheckMenuItem( debugMenu, kDebugMenuNavServicesItem, g.useNavServices );
+
+ return error;
+}
+
+/*** PARSE MENU SELECTION ***/
+OSStatus ParseMenuSelection( UInt16 menu, UInt16 item )
+{
+ // get the frontmost window, in all it's various forms
+ OSStatus error = eventNotHandledErr;
+ WindowRef window = FrontWindow();
+ WindowObjectPtr winObj = null;
+ FileWindowPtr file = null;
+ if( window )
+ {
+ winObj = (WindowObjectPtr) GetWindowRefCon( window );
+ if( GetWindowKind( window ) == kFileWindowKind )
+ file = (FileWindowPtr) winObj;
+ }
+
+ // do the menu function
+ if( menu && item )
+ {
+ switch( menu )
+ {
+ case kAppleMenu:
+ switch( item )
+ {
+ case kAppleMenuAboutItem:
+ ShowAboutBox();
+ break;
+
+ default: // something from "apple menu items" folder
+ Str255 itemName;
+ MenuHandle appleMenu = GetMenuHandle( kAppleMenu );
+ GetMenuItemText( appleMenu, item, itemName );
+ OpenDeskAcc( itemName );
+ break;
+ }
+ break;
+
+ case kFileMenu:
+ switch( item )
+ {
+ case kFileMenuNewFileItem:
+ new FileWindow;
+ break;
+
+ case kFileMenuOpenFileItem:
+ if( g.useNavServices ) DisplayOpenDialog();
+ else DisplayStandardFileOpenDialog();
+ break;
+
+ case kFileMenuCloseWindowItem:
+ if( winObj ) winObj->Close();
+ break;
+
+ case kFileMenuQuitItem:
+ AEDescList list = {};
+ error = AppleEventSendSelf( kCoreEventClass, kAEQuitApplication, list );
+ if( error ) QuitResKnife();
+ break;
+ }
+ break;
+
+ case kEditMenu:
+/* switch( item )
+ {
+ case :
+ break;
+ }
+*/ break;
+
+ case kResourceMenu:
+ switch( item )
+ {
+ case kResourceMenuNewResource:
+ if( file ) file->DisplayNewResourceDialog();
+ break;
+ }
+ break;
+
+ case kDebugMenu:
+ switch( item )
+ {
+ case kDebugMenuDebugItem:
+ g.debug = !g.debug;
+ break;
+
+ case kDebugMenuSurpressErrorsItem:
+ g.surpressErrors = !g.surpressErrors;
+ break;
+
+ case kDebugMenuAppleEventsItem:
+ g.useAppleEvents = !g.useAppleEvents;
+ break;
+
+ case kDebugMenuAppearanceItem:
+ g.useAppearance = !g.useAppearance;
+ break;
+
+ case kDebugMenuNavServicesItem:
+ g.useNavServices = !g.useNavServices;
+ break;
+ }
+ break;
+ }
+ error = UpdateMenus( FrontWindow() );
+ }
+ return error;
+}
+
+#endif
+
+ /****************/
+ /* APPLE EVENTS */
+/****************/
+
+/*** SEND MYSELF AN APPLE EVENT ***/
+OSStatus AppleEventSendSelf( DescType eventClass, DescType eventID, AEDescList list )
+{
+ OSStatus error;
+ AEAddressDesc myAddress; // all of these are really of type AEDesc
+ AppleEvent noReply;
+ AppleEvent event;
+
+ myAddress.descriptorType = typeNull;
+ myAddress.dataHandle = null;
+ noReply.descriptorType = typeNull;
+ noReply.dataHandle = null;
+
+ ProcessSerialNumber psn;
+ error = GetCurrentProcess( &psn );
+ if( error ) return error;
+
+ error = AECreateDesc( typeProcessSerialNumber, &psn, sizeof(ProcessSerialNumber), &myAddress );
+ if( error ) return error;
+
+ error = AECreateAppleEvent( eventClass, eventID, &myAddress, kAutoGenerateReturnID, kAnyTransactionID, &event );
+ if( error ) return error;
+
+ error = AEPutParamDesc( &event, keyDirectObject, &list );
+ if( error ) return error;
+
+ error = AESend( &event, &noReply, kAENoReply, kAENormalPriority, kAEDefaultTimeout, null, null );
+ return error;
+}
+
+/*** GOT REQUIRED PARAMS ***/
+Boolean GotRequiredParams( const AppleEvent *event )
+{
+ OSStatus error = noErr;
+ DescType actualType;
+ Size actualSize;
+
+ // check if we have retreived the required parameters
+ error = AEGetAttributePtr( event, keyMissedKeywordAttr, typeWildCard, &actualType, null, 0, &actualSize );
+ return error == errAEDescNotFound;
+}
+
+/*** OPEN ***/
+OSStatus AppleEventOpen( const AppleEvent *event )
+{
+ OSStatus error = noErr;
+
+ // get the list of objects to open
+ AEDescList list;
+ AEKeyword aeKeyword;
+ aeKeyword = keyDirectObject;
+ error = AEGetParamDesc( event, aeKeyword, typeAEList, &list );
+ if( !GotRequiredParams( event ) || error ) return errAEEventNotHandled;
+
+ // count how many we have
+ SInt32 itemCount;
+ error = AECountItems( &list, &itemCount );
+ if( error ) return errAEEventNotHandled;
+
+ // open each one
+ FSSpec fileSpec;
+ DescType actualType;
+ Size actualSize;
+ for( SInt32 n = 1; n <= itemCount; n++ )
+ {
+ error = AEGetNthPtr( &list, n, typeFSS, &aeKeyword, &actualType, (Ptr) &fileSpec, sizeof(FSSpec), &actualSize );
+ if( actualType == typeFSS && !error ) new FileWindow( &fileSpec );
+ }
+
+ AEDisposeDesc( &list );
+ return error;
+}
+
+/*** PRINT ***/
+OSStatus AppleEventPrint( const AppleEvent *event )
+{
+ #pragma unused( event )
+ return errAEEventNotHandled;
+}
+
+ /************************/
+ /* NIB WINDOW MANAGMENT */
+/************************/
+
+/*** SHOW ABOUT BOX ***/
+OSStatus ShowAboutBox( void )
+{
+#if TARGET_API_MAC_CARBON
+#if USE_NIBS
+ // create a nib reference (only searches the application bundle)
+ IBNibRef nibRef = null;
+ OSStatus error = CreateNibReference( CFSTR("ResKnife"), &nibRef );
+ if( error != noErr || nibRef == null )
+ {
+ DisplayError( "\pThe nib file reference could not be obtained." );
+ return error;
+ }
+
+ // create window
+ WindowRef window;
+ error = CreateWindowFromNib( nibRef, CFSTR("About Box"), &window );
+ if( error != noErr || window == null )
+ {
+ DisplayError( "\pThe about box could not be obtained from the nib file." );
+ return error;
+ }
+
+ // dispose of nib ref
+ DisposeNibReference( nibRef );
+
+ // show window
+ ShowWindow( window );
+#else
+ Rect creationBounds;
+ WindowRef window;
+ SetRect( &creationBounds, 0, 0, 300, 300 );
+ OffsetRect( &creationBounds, 50, 50 );
+ OSStatus error = CreateNewWindow( kDocumentWindowClass, kWindowStandardDocumentAttributes | kWindowStandardHandlerAttribute | kWindowInWindowMenuAttribute, &creationBounds, &window );
+
+ ControlRef picControl;
+ ControlButtonContentInfo content;
+/* content.contentType = kControlContentPictHandle;
+ content.u.picture = GetPicture( 128 );
+ if( content.u.picture == null ) DebugStr("\ppicture == null");
+*/ content.contentType = kControlContentPictRes;
+ content.u.resID = 128;
+ CreatePictureControl( window, &creationBounds, &content, true, &picControl );
+// SetControlData( picControl, kControlPicturePart, kControlPictureHandleTag, sizeof(content.u.picture), content.u.picture );
+#endif
+#else
+ WindowRef window = null;
+ if( g.useAppearance && g.systemVersion >= kMacOSEight )
+ window = GetNewCWindow( kFileWindow8, null, kFirstWindowOfClass );
+ else
+ window = GetNewCWindow( kFileWindow7, null, kFirstWindowOfClass );
+ if( window == null ) return paramErr;
+ PicHandle picture = (PicHandle) GetPicture( 128 );
+ SetWindowPic( window, picture );
+#endif
+
+ ShowWindow( window );
+ return noErr;
+}
+
+/*** SHOW PREFERENCES WINDOW ***/
+OSStatus ShowPrefsWindow( void )
+{
+#if USE_NIBS
+ // create a nib reference (only searches the application bundle)
+ IBNibRef nibRef = null;
+ OSStatus error = CreateNibReference( CFSTR("ResKnife"), &nibRef );
+ if( error != noErr || nibRef == null )
+ {
+ DisplayError( "\pThe nib file reference could not be obtained." );
+ return error;
+ }
+
+ // create window
+ WindowRef window;
+ error = CreateWindowFromNib( nibRef, CFSTR("Preferences"), &window );
+ if( error != noErr || window == null )
+ {
+ DisplayError( "\pThe preferences window could not be obtained from the nib file." );
+ return error;
+ }
+
+ // dispose of nib ref
+ DisposeNibReference( nibRef );
+
+ // install tabs handler
+ ControlRef control;
+ ControlID id = { 'tabs', 1 };
+ GetControlByID( window, &id, &control );
+ EventTypeSpec event = { kEventClassControl, kEventControlHit };
+ InstallEventHandler( GetControlEventTarget(control), NewEventHandlerUPP( PrefsTabEventHandler ), 1, &event, &control, null );
+
+ // show window
+ ShowWindow( window );
+#endif
+ return noErr;
+}
+
+/*** PREFS TAB EVENT HANDLER ***/
+pascal OSStatus PrefsTabEventHandler( EventHandlerCallRef handlerRef, EventRef event, void* userData )
+{
+ #pragma unused( handlerRef, event )
+
+ // get tabs control
+ OSStatus error = noErr;
+ ControlRef control = (ControlRef) userData;
+ SInt16 selectedTab = GetControlValue( control );
+ WindowRef window = GetControlOwner( control );
+
+ // select correct tab
+ ControlRef currentTab;
+ ControlID id = { 'pane', 1 };
+ GetControlByID( window, &id, &currentTab );
+ SetControlVisibility( currentTab, selectedTab == id.id, true );
+ id.id = 2;
+ GetControlByID( window, &id, &currentTab );
+ SetControlVisibility( currentTab, selectedTab == id.id, true );
+
+ return error;
+}
178 Carbon/Classes/Application.h
@@ -0,0 +1,178 @@
+#include "ResKnife.h"
+
+#ifndef _ResKnife_Application_
+#define _ResKnife_Application_
+
+/*!
+ @header Application
+ @discussion The Application.cpp file manages all critical workings to keep the program running, these include initalizing the environment, maintaining an event queue and parsing received events. It also manages the menubar.
+*/
+
+ /***********************/
+ /* EVENT INITALIZATION */
+/***********************/
+
+/*!
+ @function InitToolbox
+*/
+OSStatus InitToolbox( void );
+/*!
+ @function InitMenubar
+*/
+OSStatus InitMenubar( void );
+/*!
+ @function InitAppleEvents
+*/
+OSStatus InitAppleEvents( void );
+/*!
+ @function InitCarbonEvents
+*/
+OSStatus InitCarbonEvents( void );
+/*!
+ @function InitGlobals
+*/
+OSStatus InitGlobals( void );
+
+ /*****************/
+ /* EVENT PARSING */
+/*****************/
+
+#if !TARGET_API_MAC_CARBON
+
+/*!
+ @function ParseEvents
+*/
+OSStatus ParseEvents( EventRecord *event );
+/*!
+ @function ParseDialogEvents
+*/
+pascal Boolean ParseDialogEvents( DialogPtr dialog, EventRecord *event, DialogItemIndex *itemHit );
+/*!
+ @function ParseOSEvents
+*/
+OSStatus ParseOSEvents( EventRecord *event );
+
+#endif
+
+/*!
+ @function ParseAppleEvents
+*/
+pascal OSErr ParseAppleEvents( const AppleEvent *event, AppleEvent *reply, SInt32 refCon );
+
+ /******************/
+ /* EVENT HANDLING */
+/******************/
+
+#if !TARGET_API_MAC_CARBON
+
+/*!
+ @function MouseDownEventOccoured
+*/
+OSStatus MouseDownEventOccoured( EventRecord *event );
+/*!
+ @function MouseUpEventOccoured
+*/
+OSStatus MouseUpEventOccoured( EventRecord *event );
+/*!
+ @function KeyDownEventOccoured
+*/
+OSStatus KeyDownEventOccoured( EventRecord *event );
+/*!
+ @function KeyRepeatEventOccoured
+*/
+OSStatus KeyRepeatEventOccoured( EventRecord *event );
+/*!
+ @function KeyUpEventOccoured
+*/
+OSStatus KeyUpEventOccoured( EventRecord *event );
+/*!
+ @function UpdateEventOccoured
+*/
+OSStatus UpdateEventOccoured( EventRecord *event );
+/*!
+ @function ActivateEventOccoured
+*/
+OSStatus ActivateEventOccoured( EventRecord *event );
+/*!
+ @function IdleEvent
+*/
+OSStatus IdleEvent( void );
+
+#endif
+
+/*!
+ @function QuitResKnife
+*/
+void QuitResKnife( void );
+
+ /*****************/
+ /* MENU HANDLING */
+/*****************/
+
+#if TARGET_API_MAC_CARBON
+
+/*!
+ @function CarbonEventUpdateMenus
+*/
+pascal OSStatus CarbonEventUpdateMenus( EventHandlerCallRef callRef, EventRef event, void *userData );
+/*!
+ @function CarbonEventParseMenuSelection
+*/
+pascal OSStatus CarbonEventParseMenuSelection( EventHandlerCallRef callRef, EventRef event, void *userData );
+/*!
+ @function DefaultIdleTimer
+*/
+pascal void DefaultIdleTimer( EventLoopTimerRef timer, void *data );
+
+#else
+
+/*!
+ @function UpdateMenus
+*/
+OSStatus UpdateMenus( WindowRef window );
+/*!
+ @function ParseMenuSelection
+*/
+OSStatus ParseMenuSelection( UInt16 menu, UInt16 item );
+
+#endif
+
+ /****************/
+ /* APPLE EVENTS */
+/****************/
+
+/*!
+ @function AppleEventSendSelf
+*/
+OSStatus AppleEventSendSelf( DescType eventClass, DescType eventID, AEDescList list );
+/*!
+ @function GotRequiredParams
+*/
+Boolean GotRequiredParams( const AppleEvent *event );
+/*!
+ @function AppleEventOpen
+*/
+OSStatus AppleEventOpen( const AppleEvent *event );
+/*!
+ @function AppleEventPrint
+*/
+OSStatus AppleEventPrint( const AppleEvent *event );
+
+ /*********************/
+ /* NIBÑBASED WINDOWS */
+/*********************/
+
+/*!
+ @function ShowAboutBox
+*/
+OSStatus ShowAboutBox( void );
+/*!
+ @function ShowPrefsWindow
+*/
+OSStatus ShowPrefsWindow( void );
+/*!
+ @function PrefsTabEventHandler
+*/
+pascal OSStatus PrefsTabEventHandler( EventHandlerCallRef handlerRef, EventRef event, void* userData );
+
+#endif
1,710 Carbon/Classes/Asynchronous.cpp
@@ -0,0 +1,1710 @@
+#include "Asynchronous.h"
+
+//=======================================================================================
+// Statics
+//=======================================================================================
+static Boolean gsSHInited = false; // Flags whether Helper has been inited
+static Boolean *gsSHNeedsTime; // Pointer to app "SH needs time" flag
+
+static SHOutputVars gsSHOutVars; // Sound output variables
+static SHInputVars gsSHInVars; // Sound input variables
+
+static SndCallBackUPP gsSHPlayCompletionUPP; // UPP for SoundCallBackProc (BDM)
+static SICompletionUPP gsSHRecordCompletionUPP; // UPP for SIRecordCompletion (BDM)
+
+//=======================================================================================
+// Static prototypes
+//=======================================================================================
+static long SHNewRefNum( void );
+static OSErr SHNewOutRec( SHOutPtr *outRec );
+static pascal void SHPlayCompletion( SndChannelPtr channel, SndCommand *command );
+static pascal void SHRecordCompletion( SPBPtr inParams );
+static OSErr SHInitOutRec( SHOutPtr outRec, long refNum, Handle sound, char handleState );
+static void SHReleaseOutRec( SHOutPtr outRec );
+static OSErr SHQueueCallback( SndChannel *channel );
+static OSErr SHBeginPlayback( SHOutPtr outRec );
+static char SHGetState( Handle snd );
+static SHOutPtr SHOutRecFromRefNum( long refNum );
+static void SHPlayStopByRec( SHOutPtr outRec );
+static OSErr SHGetDeviceSettings( long inRefNum, short *numChannels, Fixed *sampleRate, short *sampleSize, OSType *compType );
+
+//=======================================================================================
+//
+// pascal OSErr SHInitSoundHelper( Boolean *attnFlag, short numChannels )
+//
+// Summary:
+// This routine initializes the Asynchronous Sound Helper.
+//
+// Scope:
+// Public.
+//
+// Parameters:
+// attnFlag Pointer to an application Boolean. This Boolean will be set to
+// true when the Helper needs a call to SHIdle. For example, the
+// application will have a global Boolean with a name like
+// "gCallHelper", and will pass the address of that global to
+// SHInitSoundHelper. Then, in the application's main event loop,
+// will simply check gCallHelper, and call SHIdle if it is set.
+// numChannels Tells the Helper how many output records to allocate. The number
+// of simultaneous sounds that can be produced by the Helper is
+// limited by a) the number of simultaneous channels the Sound
+// Manager allows, and b) the number of output records specified by
+// this parameter. If you specify zero, a reasonable default (4) is
+// used.
+//
+// Returns:
+// MemError() If there is one.
+// memFullErr If the output array was nil but MemError returned noErr.
+// noErr Otherwise.
+//
+// Operation:
+// This routine simply allocates the output records, initializes some statics, and
+// sets a flag that tells whether the Helper has been initialized.
+//
+//=======================================================================================
+pascal OSErr SHInitSoundHelper( Boolean *attnFlag, short numChannels )
+{
+ OSErr error = noErr;
+
+ // Use default number of channels if zero was specified
+ if( numChannels == 0 )
+ numChannels = kSHDefChannels;
+
+ // Remember the address of the application's "attention" flag
+ gsSHNeedsTime = attnFlag;
+
+ // Allocate the channels
+ gsSHOutVars.numOutRecs = numChannels;
+ gsSHOutVars.outArray = (SHOutPtr) NewPtrClear( numChannels * sizeof(SHOutRec) );
+
+ // Set up UPPs for play completion and input completion
+ gsSHPlayCompletionUPP = NewSndCallBackUPP( SHPlayCompletion );
+ gsSHRecordCompletionUPP = NewSICompletionUPP( SHRecordCompletion );
+
+ // If successful, flag that we're initialized and exit
+ if( gsSHOutVars.outArray != nil )
+ {
+ gsSHInited = true;
+ return error;
+ }
+ else
+ {
+ // Return some kind of error (MemError if there is one, otherwise make one up )
+ error = MemError();
+ if( error == noErr )
+ error = memFullErr;
+ return error;
+ }
+}
+
+//=======================================================================================
+//
+// pascal void SHIdle( void )
+//
+// Summary:
+// This routine performs various cleanup operations when sounds have finished
+// playing or recording.
+//
+// Scope:
+// Public.
+//
+// Parameters:
+// None.
+//
+// Returns:
+// Nothing.
+//
+// Operation:
+// First, SHIdle clears the flag that indicates an SHIdle call is needed. Next,
+// SHIdle performs playback cleanup. It iterates through the output records
+// looking for records that are both in use and complete. Such records are disposed
+// with SHReleaseOutRec. This frees the record for use later, and closes the sound
+// channel with the Sound Manager. Next, SHIdle performs recording cleanup. It
+// checks if recording is underway and is flagged as complete. If so, it unlocks
+// the (previously locked) input handle, and checks for errors. If errors occurred,
+// the input handle is disposed and the recordErr field of the input variable record
+// is filled with the error. This allows the error to later be reported to the
+// caller when he calls SHGetRecordedSound. If no error occured, then the sound
+// header is re-written into the input sound handle, this time with the correct
+// length, and the handle is sized down to match the actual number of samples that
+// were recorded. Then the sound input device is closed and the application is
+// notified that recording is complete through his Boolean completion flag.
+//
+//=======================================================================================
+pascal void SHIdle( void )
+{
+ OSErr error = noErr;
+ long realSize;
+
+ // Immediately turn off the application's "Helper needs time" flag
+ *gsSHNeedsTime = false;
+
+ // Do playback cleanup
+ for( short i = 0; i < gsSHOutVars.numOutRecs; i++ )
+ if( gsSHOutVars.outArray[i].inUse &&
+ gsSHOutVars.outArray[i].channel.userInfo == kSHComplete )
+ // We've found a channel that needs closing...
+ SHReleaseOutRec( &gsSHOutVars.outArray[i] );
+
+ // Do recording cleaunp
+ if( gsSHInVars.recording && gsSHInVars.recordComplete )
+ {
+ HUnlock( gsSHInVars.inHandle );
+
+ if( gsSHInVars.inPB.error && gsSHInVars.inPB.error != abortErr )
+ {
+ // An error (other than a manual stop) occurred during recording. Kill the
+ // handle and save the error code.
+ gsSHInVars.recordErr = gsSHInVars.inPB.error;
+ DisposeHandle( gsSHInVars.inHandle );
+ gsSHInVars.inHandle = nil;
+ }
+ else
+ {
+ // Recording completed normally (which includes abortErr, the "error" that
+ // occurs when recording is manually stopped). We re-write the header (to
+ // slam the correct size in there), and size the handle to fit the actual
+ // recorded size (which either shortens the handle, or doesn't change its
+ // size -- that's why we don't bother checking the error).
+ gsSHInVars.recordErr = noErr;
+ realSize = gsSHInVars.inPB.count + gsSHInVars.headerLength;
+ error = SetupSndHeader( (SndListHandle) gsSHInVars.inHandle, gsSHInVars.numChannels,
+ gsSHInVars.sampleRate, gsSHInVars.sampleSize, gsSHInVars.compType,
+ kSHBaseNote, realSize, &gsSHInVars.headerLength );
+ SetHandleSize( gsSHInVars.inHandle, realSize ); // Shorten the handle
+ }
+
+ // Error or not, close the recording device, and notify the application that
+ // recording is complete, through the recording-completed flag that the caller
+ // originally passed into SHRecordStart.
+ SPBCloseDevice( gsSHInVars.inRefNum );
+ gsSHInVars.recording = false;
+ gsSHInVars.inRefNum = 0;
+ if( gsSHInVars.appComplete != nil )
+ *gsSHInVars.appComplete = true;
+ }
+}
+
+//=======================================================================================
+//
+// pascal void SHKillSoundHelper( void )
+//
+// Summary:
+// This routine terminates the Asynchronous Sound Helper.
+//
+// Scope:
+// Public.
+//
+// Parameters:
+// None.
+//
+// Returns:
+// Nothing.
+//
+// Operation:
+// This routine, after checking that the Helper was previously initialized, stops
+// all current playback and recording, waits 1/60th of a second (to allow the Sound
+// Manager to call our callback routines, SHPlayCompletion and SHRecordCompletion),
+// then calls SHIdle to force cleanup (releasing sound channels and closing the
+// sound input device if appropriate). Then, SHKillSoundHelper disposes of the
+// output records.
+//
+//=======================================================================================
+pascal void SHKillSoundHelper( void )
+{
+ unsigned long timeout;
+ Boolean outputClean, inputClean;
+
+ if( !gsSHInited )
+ return;
+
+ SHPlayStopAll(); // Kill all playback
+ SHRecordStop(); // Kill recording
+
+ // Now sync-wait for everything to clean itself up
+ timeout = TickCount() + kSHSyncWaitTimeout;
+ do {
+ if( *gsSHNeedsTime )
+ SHIdle(); // Clean up when required
+
+ // Check if all our output channels are cleaned up
+ outputClean = true;
+ for( short i = 0; i < gsSHOutVars.numOutRecs && outputClean; i++ )
+ if( gsSHOutVars.outArray[i].inUse )
+ outputClean = false;
+
+ // Check whether our recording is cleaned up
+ inputClean = !gsSHInVars.recording;
+
+ if( inputClean && outputClean )
+ break;
+ } while (TickCount() < timeout );
+
+ // Lose our preallocated sound channels
+ DisposePtr( (Ptr) gsSHOutVars.outArray );
+}
+
+//=======================================================================================
+//
+// long SHNewRefNum( void )
+//
+// Summary:
+// This routine returns the next available output reference number.
+//
+// Scope:
+// Private.
+//
+// Parameters:
+// None.
+//
+// Returns:
+// The next available output reference number.
+//
+// Operation:
+// The output variable nextRef contains the next available reference number. This
+// function returns the value of nextRef then increments it. This way, output ref-
+// erence numbers are unique throughout a session (modulo 2,147,483,647).
+//
+//=======================================================================================
+long SHNewRefNum( void )
+{
+ return gsSHOutVars.nextRef++;
+}
+
+//=======================================================================================
+//
+// OSErr SHNewOutRec( SHOutPtr *outRec )
+//
+// Summary:
+// This routine attempts to return the first available output record.
+//
+// Scope:
+// Private.
+//
+// Parameters:
+// outRec A pointer to an SHOutRecPtr. This is where SHNewOutRec puts
+// the pointer to the output record.
+//
+// Returns:
+// kSHErrOutaChannels If no free output record was found.
+// noErr Otherwise.
+//
+// Operation:
+// SHNewOutRec simply iterates through the output record array looking for a record
+// that is not in use. If all the records are in use, SHNewOutRec returns the
+// Helper error code kSHErrOutaChannels. If a record is found, then the address of
+// the record is stored via the VAR parameter, and noErr is returned.
+//
+//=======================================================================================
+OSErr SHNewOutRec( SHOutPtr *outRec )
+{
+ // First look for a free channel among our preallocated output records
+ for( short i = 0; i < gsSHOutVars.numOutRecs; i++ )
+ if( !gsSHOutVars.outArray[i].inUse )
+ {
+ *outRec = &gsSHOutVars.outArray[i];
+ return noErr;
+ }
+
+ return kSHErrOutaChannels;
+}
+
+//=======================================================================================
+//
+// pascal void SHPlayCompletion( SndChannelPtr channel, SndCommand *command )
+//
+// Summary:
+// This routine is the playback callback routine we provide to the Sound Manager.
+//
+// Scope:
+// Private.
+//
+// Parameters:
+// channel A pointer to the sound channel that is calling back. It is calling
+// back because we queued up a callbackCmd.
+// command A pointer to the actual command that caused us to be called back.
+// This command happens to have important information (like the app's
+// A5, and a constant we can use to verify that this is a "real" call-
+// back, as opposed to one that has been erroroneously generated by the
+// Sound Manager).
+//
+// Returns:
+// Nothing.
+//
+// Operation:
+// This routine first looks for our "completion signature." This is how we know
+// that the callback really means the sound has completed. There is a bug in the
+// Sound Manager that may cause callbacks that weren't specifically requested, and
+// this constant allows us to distinguish our "real" callback from one that is a
+// result of that bug. If the callback is hip, then we set up A5 (so we can ref-
+// erence our globals), and set the application's attention flag. Also, we set
+// the channel's userInfo field to a constant that SHIdle will recognize, so SHIdle
+// will know the sound on that channel has completed, and that the channel can be
+// freed.
+//
+//=======================================================================================
+pascal void SHPlayCompletion( SndChannelPtr channel, SndCommand *command )
+{
+ // Look for our "callback signature" in the sound command.
+ if( command->param1 == kSHCompleteSig )
+ {
+ channel->userInfo = kSHComplete;
+ *gsSHNeedsTime = true; // Tell the app to give us an SHIdle call
+ }
+}
+
+//=======================================================================================
+//
+// pascal void SHRecordCompletion( SPBPtr inParams )
+//
+// Summary:
+// This routine is the recording callback routine we provide to the Sound Manager.
+//
+// Scope:
+// Private.
+//
+// Parameters:
+// inParams This points to the input sound parameter block that has completed
+// recording.
+//
+// Returns:
+// Nothing.
+//
+// Operation:
+// When recording completes for any reason (error, consumed all the memory that was
+// provided, abort, etc.) then the Sound Manager calls our callback routine. This
+// routine first grabs A5 from the SPB's userLong field and sets us up to use our
+// globals. Then, it sets the application's "Helper needs time" Boolean, and sets
+// our internal flag that recording has completed (so SHIdle will know to close the
+// recording device, etc. )
+//
+//=======================================================================================
+pascal void SHRecordCompletion( SPBPtr inParams )
+{
+ #pragma unused( inParams )
+ *gsSHNeedsTime = true; // Notify the app to give us time
+ gsSHInVars.recordComplete = true; // Make a note to ourselves, too
+}
+
+//=======================================================================================
+//
+// OSErr SHInitOutRec( SHOutPtr outRec, long refNum, Handle sound, char handleState )
+//
+// Summary:
+// This routine is used to fill out an SHOutRec and call SndNewChannel.
+//
+// Scope:
+// Private.
+//
+// Parameters:
+// outRec A pointer to the SHOutRec we're filling out.
+// refNum The output reference number we'll give back to the caller.
+// sound A locked, non-purgeable handle to a (hopefully) valid sound.
+// handleState The original handle state, before the HLock and HNoPurge. This
+// allows SHReleaseOutRec to properly reset the handle's flags when
+// playback is complete.
+//
+// Returns:
+// OSErr Error results of SndNewChannel call, if an error occurred.
+// noErr Otherwise.
+//
+// Operation:
+// This routine is called to fill out a SHOutRec. First, it clears the SndChannel
+// within the SHOutRec to all zeros. It then installs the default queue size, and
+// calls the Sound Manager routine SndNewChannel. If an error occurs, we return
+// it right away. If not, then we fill out the rest of the fields in the SHOutRec.
+// Note that in the call to SndNewChannel, we specify NO SYNTHESIZER, and NO INIT-
+// IALIZATION. This is because the Helper is a "sound service," and has no freakin'
+// idea what kind of sound the caller is going to try to play using this channel.
+// So, we assume nothing. Also note that we provide our completion routine,
+// SHPlayCompletion, to SndNewChannel.
+//
+//=======================================================================================
+OSErr SHInitOutRec( SHOutPtr outRec, long refNum, Handle sound, char handleState )
+{
+ OSErr error;
+ SndChannelPtr channel;
+
+ // Initialize the sound channel inside outRec. We'll clear the bytes to zero,
+ // install the proper queue size, then call SndNewChannel.
+ for( unsigned short i = 0; i < sizeof(SndChannel); i++ )
+ ((char *) &outRec->channel)[i] = 0;
+ outRec->channel.qLength = stdQLength;
+ channel = &outRec->channel;
+ error = SndNewChannel( &channel, kSHNoSynth, kSHNoInit, gsSHPlayCompletionUPP );
+ if( error != noErr )
+ return error;
+
+ // Initialize the rest of the record and return noErr. Note that we only set the
+ // record's inUse flag if the SndNewChannel call was successful.
+ outRec->refNum = refNum;
+ outRec->sound = sound;
+ outRec->rate = 0;
+ outRec->handleState = handleState;
+ outRec->inUse = true;
+ outRec->paused = false;
+ return error;
+}
+
+//=======================================================================================
+//
+// void SHReleaseOutRec( SHOutPtr outRec )
+//
+// Summary:
+// This routine "releases," or frees up an output record.
+//
+// Scope:
+// Private.
+//
+// Parameters:
+// outRec A pointer to the output record we want to release.
+//
+// Returns:
+// Nothing.
+//
+// Operation:
+// If the output record's inUse flag is set, that means that SndNewChannel has been
+// called for it. In that case, we call SndDisposeChannel to allow the Sound Man-
+// ager to dispose it's internal data structures for this channel. Either way, if
+// there's an associated sound, we check whether the sound is playing on any other
+// channel, and if not, we reset it's handle flags. Finally, we clear the record's
+// inUse flag, thereby allowing it to be reused.
+//
+//=======================================================================================
+void SHReleaseOutRec( SHOutPtr outRec )
+{
+ Boolean found = false;
+
+ // An SHOutRec's inUse flag only gets set if SndNewChannel has been called on the
+ // record's sound channel. So if it is in use, we try a call to SndDisposeChannel,
+ // and ignore the error (what else can we do? )
+ if( outRec->inUse )
+ SndDisposeChannel( &outRec->channel, kSHQuietNow );
+
+ // If this sound handle isn't being used by some other output record, kindly restore
+ // the original handle state.
+ if( outRec->sound != nil )
+ {
+ for( short i = 0; i < gsSHOutVars.numOutRecs && !found; i++ )
+ if( &gsSHOutVars.outArray[i] != outRec && gsSHOutVars.outArray[i].inUse &&
+ gsSHOutVars.outArray[i].sound == outRec->sound )
+ found = true;
+
+ if( !found )
+ HSetState( outRec->sound,outRec->handleState );
+ }
+
+ outRec->inUse = false;
+}
+
+//=======================================================================================
+//
+// OSErr SHQueueCallback( SndChannel *channel )
+//
+// Summary:
+// This routine queues up a verifyable callback in the given sound channel.
+//
+// Scope:
+// Private:
+//
+// Parameters:
+// channel The sound channel we want a callback from.
+//
+// Returns:
+// OSErr Results of the SndDoCommand call.
+//
+// Operation:
+// This routine queues up a sound command in the given channel that will cause a
+// callback. This is how we'll know the sound completed. In order to make the
+// callback verifyable, we stuff kSHCompleteSig into param1. We can test for this
+// value within the callback routine. Also, since the poor callback is called at
+// interrorupt time and can't count on its A5 world, we provide the application A5
+// in param2.
+//
+//=======================================================================================
+OSErr SHQueueCallback( SndChannel *channel )
+{
+ SndCommand command;
+
+ command.cmd = callBackCmd;
+ command.param1 = kSHCompleteSig; // To make the callback verifyable.
+ command.param2 = 0L;
+
+ return SndDoCommand( channel, &command, kSHWait );
+}
+
+//=======================================================================================
+//
+// OSErr SHBeginPlayback(SHOutPtr outRec )
+//
+// Summary:
+// This routine begins playback of the sound that's installed in the given SHOutRec.
+//
+// Scope:
+// Private.
+//
+// Parameters:
+// outRec The output record that we want to start playback on.
+//
+// Returns:
+// OSErr Error results of SndPlay, if an error occurred.
+// noErr Otherwise.
+//
+// Operation:
+// This routine calls SndPlay (asynchronously) for the sound that is installed in
+// the given output record. This begins playback. Immediately following that, we
+// queue up a callback, so that when the sound that we just start completes, we'll
+// get a callback (to SHPlayCompletion), and we'll know that the channel needs to
+// be released.
+//
+// IMPORTANT:
+// This routine is called from SHPlayByID, and SHPlayByHandle when a sound handle is
+// provided. The purpose of these routines is to trigger a sound, and if you call
+// SHPlayByID or SHPlayByHandle that way, DON'T use SHGetChannel to get the sound
+// channel Helper is using to play the sound, then subsequently call SndPlay your-
+// self to play some other sound. Why not? There is a bug in pre-7.0 Systems that
+// causes a crash if more than one SndPlay call is made on the same channel. Helper
+// will never do this on its own, and you shouldn't either. If you want a sound
+// channel that you want to send commands to, call SHPlayByHandle with a nil handle,
+// then call SHGetChannel to retreive a pointer to the channel.
+//
+//=======================================================================================
+OSErr SHBeginPlayback( SHOutPtr outRec )
+{
+ OSErr error = noErr;
+
+ // First, initiate playback. If an error occurs, return it immediately.
+ error = SndPlay( &outRec->channel, (SndListHandle)outRec->sound, kSHAsync );
+ if( error != noErr )
+ return error;
+
+ // Playback started okay. Let's queue up a callback command so we'll know when
+ // the sound is finished.
+ SHQueueCallback( &outRec->channel ); // ignore error (what can we do? )
+ return error;
+}
+
+//=======================================================================================
+//
+// char SHGetState( Handle snd )
+//
+// Summary:
+// This routine is a local replacement for HGetState which tries to find snd in an
+// existing output record.
+//
+// Scope:
+// Private:
+//
+// Parameters:
+// snd The handle we want the handle state for
+//
+// Returns:
+// A char representing the handle flags (either currently or from some existing
+// output record).
+//
+// Operation:
+// This routine searches the output record array for an output record that is both
+// in use AND has a "sound" field equal to the parameter snd. What this means is
+// that we've found an output record that is currently playing snd. If we find
+// such a record, we return the "handleState" field of that output record. If no
+// such record is found, then we return the results of HGetState(snd). The reason
+// we need this is that you could re-trigger a sound (that is, play the same sound
+// simultaneously on more than one Helper output channel). In such a case, the
+// first SHPlayByID or (ByHandle) call would get the actual handle state (from
+// HGetState). If another SHPlayByID call came in while the original was still
+// playing, the handleState from the existing output record would be retured. This
+// way, the second Play doesn't save a "locked" state and restore to a locked state
+// when the sound has completed.
+//
+//=======================================================================================
+char SHGetState( Handle snd )
+{
+ // Look for an output record that has snd for a sound. If one is found, grab and
+ // return its handleState instead of the handle's current flags.
+ for( short i = 0; i < gsSHOutVars.numOutRecs; i++ )
+ if( gsSHOutVars.outArray[i].inUse && gsSHOutVars.outArray[i].sound == snd )
+ return gsSHOutVars.outArray[i].handleState;
+
+ return HGetState(snd);
+}
+
+//=======================================================================================
+//
+// pascal OSErr SHPlayByID( short resID, long *refNum )
+//
+// Summary:
+// This routine begins asynchronous playback of the 'snd ' resource with ID resID.
+//
+// Scope:
+// Public.
+//
+// Parameters:
+// resID The resource ID of the 'snd ' resource the caller wants to play back.
+// refNum A pointer to where to store the output reference number. THIS IS
+// OPTIONAL. If nil is specified, the refNum is not returned.
+//
+// Returns:
+// ResError() If the GetResource failed, and ResError gave an error.
+// resNotFound If the GetResource failed, but ResError was noErr.
+// kSHErrOutaChannels If the SHNewOutRec call failed.
+// OSErr If the SHInitOutRec call failed.
+// OSErr If the SHBeginPlayback call failed.
+// noErr Otherwise.
+//
+// Operation:
+// This routine plays the 'snd ' resource referrored to by ID resID. First, we try
+// to load the sound resource. If successful, we note the handle state of the
+// sound handle, and set it to be nonpurgeable (because we don't want subsequent
+// operations, namely the SndNewChannel call that happens in SHInitOutRec, to wipe
+// out the sound we so carefully read into memory). Then we get a reference number
+// and a pointer to the next free output record. If successful, we initialize the
+// output record (and open the sound channel) with SHInitOutRec. If successful,
+// we move the sound handle as high in the heap as we can (to help avoid fragmen-
+// tation), and lock it. Then we call SHBeginPlayback to start the sound playing
+// and queue up a callback so we'll know when it's done. If successful, we return
+// the reference number (if the caller wants it).
+//
+// IMPORTANT:
+// DO NOT start a sound playing with SHPlayByID, get it's channel with SHGetChannel,
+// and then do another SndPlay on that channel! This will crash on pre-7.0 systems.
+// If you want a channel, use SHPlayByHandle with a nil handle. See the comments
+// for SHPlayByHandle and SHBeginPlayback.
+//
+//=======================================================================================
+pascal OSErr SHPlayByID( short resID, long *refNum )
+{
+ Handle sound;
+ char oldhandleState;
+ short ref;
+ OSErr error;
+ SHOutPtr outRec;
+
+ // First, try to get the caller's 'snd ' resource. If we can't return ResError().
+ // If we DO get the sound, save it's flags, then set it to be nonpurgeable. This
+ // is because some of the Sound Manager stuff below may cause memory allocation,
+ // which could cause the sound to be purged. We don't want that, since we're
+ // going to start playing it real soon.
+ sound = GetResource( soundListRsrc, resID );
+ if( sound == nil )
+ {
+ error = ResError();
+ if( error == noErr )
+ error = resNotFound;
+ return error;
+ }
+ oldhandleState = SHGetState(sound );
+ HNoPurge(sound );
+
+ // Now let's get a reference number and an output record.
+ ref = SHNewRefNum();
+ error = SHNewOutRec( &outRec );
+ if( error != noErr )
+ {
+ HSetState( sound, oldhandleState );
+ return error;
+ }
+
+ // Now let's fill in the output record with all the pertinent information. This
+ // routine also initializes the sound channel and flags outRec as "in use."
+ error = SHInitOutRec(outRec, ref, sound, oldhandleState );
+ if( error != noErr )
+ {
+ HSetState( sound, oldhandleState );
+ SHReleaseOutRec( outRec );
+ return error;
+ }
+
+ // At this point, we're in pretty good shape. We've got a reference number, an
+ // initialized output record, and the sound handle. Let's party.
+ MoveHHi( sound );
+ HLock( sound );
+ error = SHBeginPlayback( outRec );
+ if( error != noErr )
+ {
+ HSetState( sound, oldhandleState );
+ SHReleaseOutRec( outRec );
+ return error;
+ }
+ else
+ {
+ if( refNum != nil ) // refNum is optional -- the caller may not want it
+ *refNum = ref;
+ return error;
+ }
+}
+
+//=======================================================================================
+//
+// pascal OSErr SHPlayByHandle( Handle sound, long *refNum )
+//
+// Summary:
+// This routine begins asynchronous playback of a sound provided in a handle.
+//
+// Scope:
+// Public.
+//
+// Parameters:
+// sound A handle to the sound the caller wants to play. This may optionally
+// be nil, indicating that the sound channel should be opened, but no
+// SndPlay call should be made. If a caller does this, he usually calls
+// SHGetChannel to get a pointer to the sound channel, so he can send
+// sound commands to the channel.
+// refNum A pointer to where to store the output reference number. THIS IS
+// OPTIONAL. If nil is passed, the reference number is not returned.
+//
+// Returns:
+// kSHErrOutaChannels If the SHNewOutRec call failed.
+// OSErr If the SHInitOutRec call failed.
+// OSErr If the SHBeginPlayback call failed.
+// noErr Otherwise.
+//
+// Operation:
+// If a handle is provided, we set it to be nonpurgeable so that subsequent oper-
+// ations don't blow it away, and we note it's current handle state. Then, we get
+// a reference number and a pointer to a free output record. If successful, we
+// initialize the output record, thereby opening the sound channel. Then, if a
+// sound was provided, we move it high, lock it, and call SHBeginPlayback to begin
+// asynchronous playback and queue up a callback. Finally, we return the reference
+// number if the caller wants it. If the sound wasn't provided (i.e. nil), every-
+// thing is the same except there's no SHBeginPlayback call.
+//
+// IMPORTANT:
+// DO NOT start a sound handle playing with SHPlayByHandle, get it's channel with
+// SHGetChannel, and then do another SndPlay on that channel! This will crash on
+// pre-7.0 systems. If you want a channel, use SHPlayByHandle with a _NIL_ handle.
+// See the comments for SHBeginPlayback.
+//
+//=======================================================================================
+pascal OSErr SHPlayByHandle( Handle sound, long *refNum )
+{
+ char oldhandleState;
+ short ref;
+ OSErr error;
+ SHOutPtr outRec;
+
+ // Save sound handle's flags, then set it to be nonpurgeable. This is because some
+ // of the Sound Manager stuff below may cause memory allocation, which could cause
+ // the handle to be purged. We don't want that, since we're going to start playing
+ // it real soon. If the caller gave us nil for a sound handle, that means he's
+ // really just interested in having the sound channel. So, we go on our merrory way
+ // without a sound handle.
+ if( sound != nil )
+ {
+ oldhandleState = SHGetState( sound );
+ HNoPurge(sound );
+ } else oldhandleState = 0;
+
+ // Now, let's get a reference number and an output record.
+ ref = SHNewRefNum();
+ error = SHNewOutRec( &outRec );
+ if( error != noErr )
+ {
+ if( sound != nil )
+ HSetState( sound, oldhandleState );
+ return error;
+ }
+
+ // Now let's fill in the output record with all the pertinent information. This
+ // routine also initializes the sound channel and flags outRec as "in use."
+ error = SHInitOutRec( outRec, ref, sound, oldhandleState );
+ if( error != noErr )
+ {
+ if( sound != nil )
+ HSetState( sound, oldhandleState );
+ SHReleaseOutRec( outRec );
+ return error;
+ }
+
+ // At this point, we're in pretty good shape. We've got a reference number, an
+ // initialized output record, and the sound handle. Let's get whacky.
+ if( sound != nil )
+ { // if we've got a sound, lock and begin playback
+ MoveHHi( sound );
+ HLock( sound );
+ error = SHBeginPlayback( outRec );
+ if( error != noErr )
+ {
+ HSetState( sound, oldhandleState );
+ SHReleaseOutRec( outRec );
+ return error;
+ }
+ else
+ {
+ if( refNum != nil ) // refNum is optional - the caller may not want it
+ *refNum = ref;
+ return error;
+ }
+ }
+ else
+ { // if there's no sound, go ahead and return noErr
+ if( refNum != nil ) // refNum is optional - the caller may not want it
+ *refNum = ref;
+ return error;
+ }
+}
+
+//=======================================================================================
+//
+// SHOutPtr SHOutRecFromRefNum( long refNum )
+//
+// Summary:
+// This routine finds that SHOutRec that is associated with a given refNum, if any.
+//
+// Scope:
+// Private.
+//
+// Parameters:
+// refNum The output reference number in question.
+//
+// Returns:
+// A pointer to the associated SHOutRec, if any, or nil, if none was found with a
+// reference number matching refNum.
+//
+// Operation:
+// This handy routine searches the output record array looking for an output record
+// that has the given reference number. If one is found, a pointer to it is
+// returned. If not, then nil is returned.
+//
+//=======================================================================================
+SHOutPtr SHOutRecFromRefNum( long refNum )
+{
+ short i;
+
+ // Search for the specified refNum
+ for( i = 0; i < gsSHOutVars.numOutRecs; i++ )
+ if( gsSHOutVars.outArray[i].inUse && gsSHOutVars.outArray[i].refNum == refNum )
+ break;
+
+ // If we found it, return a pointer to that record, otherwise, nil.
+ if( i == gsSHOutVars.numOutRecs )
+ return nil;
+ else return &gsSHOutVars.outArray[i];
+}
+
+//=======================================================================================
+//
+// void SHPlayStopByRec( SHOutPtr outRec )
+//
+// Summary:
+// This routine stops sound playback on the channel associated with the given
+// output record.
+//
+// Scope:
+// Private.
+//
+// Parameters:
+// outRec A pointer to the output record whose sound should be stopped.
+//
+// Returns:
+// Nothing.
+//
+// Operation:
+// This routine sends two immediate sound commands to the channel in the given
+// output record. The flushCmd gets rid of any unprocessed commands from the
+// queue subsequent to the one currently being processed. The quietCmd, when sent
+// with SndDoImmediate, immediately quiets the channel. Note that there might not
+// be any synthesizer yet associate with this channel, and in that case, these
+// commands are just eaten by the Sound Manager.
+//
+//=======================================================================================
+void SHPlayStopByRec( SHOutPtr outRec )
+{
+ SndCommand cmd;
+
+ // Dump the rest of the commands in the queue (including our callbackCmd).
+ cmd.cmd = flushCmd;
+ cmd.param1 = 0;
+ cmd.param2 = 0;
+ SndDoImmediate( &outRec->channel, &cmd );
+
+ // Shut up this minute! Go to your room! No dessert tonight for you, little boy.
+ cmd.cmd = quietCmd;
+ cmd.param1 = 0;
+ cmd.param2 = 0;
+ SndDoImmediate( &outRec->channel, &cmd );
+
+ // It is now safe to just manually dump our channel (we'll just skip the whole
+ // callback thing in this case).
+ SHReleaseOutRec( outRec );
+}
+
+//=======================================================================================
+//
+// pascal OSErr SHPlayStop( long refNum )
+//
+// Summary:
+// This routine stops playback on the output record referrored to by refNum.
+//
+// Scope:
+// Public.
+//
+// Parameters:
+// refNum The output reference number of the sound the caller wants stopped.
+//
+// Returns:
+// kSHErrBadRefNum If the reference number does not refer to any current output
+// record. (Note that this is not necessarily bad. If they
+// try to stop a sound that has already stopped by its own
+// accord, this error will be returned. Usually you can call
+// this routine and ignore the error. )
+// noErr Otherwise.
+//
+// Operation:
+// This routine calls SHOutRecFromRefNum to try to find the output record that is
+// associated with refNum. If one is found, we call SHPlayStopByRec to stop
+// playback for that output record.
+//
+//=======================================================================================
+pascal OSErr SHPlayStop( long refNum )
+{
+ SHOutPtr outRec;
+
+ // Look for the associated output record.
+ outRec = SHOutRecFromRefNum( refNum );
+
+ // If we found it, call SHPlayStopByRec to stop playback.
+ if( outRec != nil )
+ {
+ SHPlayStopByRec( outRec );
+ return noErr;
+ }
+ else return kSHErrBadRefNum;
+}
+
+//=======================================================================================
+//
+// pascal OSErr SHPlayStopAll( void )
+//
+// Summary:
+// This routine stops all sound that the Helper initiated.
+//
+// Scope:
+// Public.
+//
+// Parameters:
+// None.
+//
+// Returns:
+// noErr This may return something more interesting in the future.
+//
+// Operation:
+// This routine iterates through all the output records looking for records that
+// are in use. When an in-use record is found, playback on that record is stopped
+// by calling SHPlayStopByRec. Errors are ignored.
+//
+//=======================================================================================
+pascal OSErr SHPlayStopAll( void )
+{
+ // Look for output records that are in use and stop their playback with
+ // SHPlayStopByRec.
+ for( short i = 0; i < gsSHOutVars.numOutRecs; i++ )
+ if( gsSHOutVars.outArray[i].inUse )
+ SHPlayStopByRec( &gsSHOutVars.outArray[i] );
+
+ return noErr;
+}
+
+//=======================================================================================
+//
+// pascal OSErr SHPlayPause( long refNum )
+//
+// Summary:
+// This routine pauses playback of sound associated with refNum.
+//
+// Scope:
+// Public.
+//
+// Parameters:
+// refNum The output reference number of the sound the caller wants paused.
+//
+// Returns:
+// OSErr If a SndDoImmediate fails.
+// kSHErrBadRefNum If the given reference number is not associated with any
+// current output record.
+// kSHErrAlreadyPaused If the sound is already paused.
+//
+// Operation:
+// For a sound like "Simple beep," which is a long sequence of sound commands,
+// pausing a sound means "pausing sound command queue processing," and is performed
+// with the pauseCmd. Sampled sounds, like "Wild Eep," usually consist of a single
+// bufferCmd to play back the sampled sound. The pauseCmd is ineffective with
+// sampled sounds because the sound is paused after the current command is processed
+// (the bufferCmd), so the entire sound would be played. This is rarely what the
+// caller wants. So, we've got a little trick in here to pause sampled sounds. If
+// you set a sampled sound's sample playback rate to zero, it effectively pauses the
+// sampled sound in its tracks, mid-bufferCmd (which is what the caller probably
+// wants).
+//
+// There is really no officially sanctioned way to know whether a sound is command-
+// type or sampled without parsing the sound. However, any synthesizer that returns
+// a non-zero rate from a getRateCmd call will be able to understand a rateCmd. So
+// we try a getRateCmd, and if we get a non-zero rate, send a rateCmd to set the
+// playback rate to zero. If we get zero from the getRateCmd, we assume that the
+// synthesizer cannot understand the getRateCmd, and instead use a pauseCmd to pause
+