From 49bbbf24fb62270993181e42351fa8df52cb93d8 Mon Sep 17 00:00:00 2001 From: Nicholas Shanks Date: Fri, 19 Oct 2001 19:41:13 +0000 Subject: [PATCH] Initial check-in --- Bug List.text | 56 + Carbon/Classes/Application.cpp | 1132 +++++++++ Carbon/Classes/Application.h | 178 ++ Carbon/Classes/Asynchronous.cpp | 1710 +++++++++++++ Carbon/Classes/Asynchronous.h | 121 + Carbon/Classes/DataBrowser.cpp | 634 +++++ Carbon/Classes/DataBrowser.h | 87 + Carbon/Classes/EditorWindow.cpp | 57 + Carbon/Classes/EditorWindow.h | 37 + Carbon/Classes/Errors.cpp | 89 + Carbon/Classes/Errors.h | 42 + Carbon/Classes/FileWindow.cpp | 1799 ++++++++++++++ Carbon/Classes/FileWindow.h | 179 ++ Carbon/Classes/Files.cpp | 860 +++++++ Carbon/Classes/Files.h | 62 + Carbon/Classes/HostCallbacks.cpp | 186 ++ Carbon/Classes/HostCallbacks.h | 245 ++ Carbon/Classes/InspectorWindow.cpp | 265 ++ Carbon/Classes/InspectorWindow.h | 26 + Carbon/Classes/PickerWindow.cpp | 73 + Carbon/Classes/PickerWindow.h | 23 + Carbon/Classes/PlugObject.cpp | 95 + Carbon/Classes/PlugObject.h | 97 + Carbon/Classes/PlugWindow.cpp | 91 + Carbon/Classes/PlugWindow.h | 78 + Carbon/Classes/ResourceObject.cpp | 100 + Carbon/Classes/ResourceObject.h | 159 ++ Carbon/Classes/Utility.cpp | 220 ++ Carbon/Classes/Utility.h | 73 + Carbon/Classes/WindowObject.cpp | 75 + Carbon/Classes/WindowObject.h | 86 + Carbon/Generic.h | 16 + Carbon/ResKnife.h | 310 +++ Carbon/ResKnife.lib | Bin 0 -> 74319 bytes Carbon/Resources/Carbon.r | 1 + Carbon/Resources/ResKnife.nib/classes.nib | 295 +++ Carbon/Resources/ResKnife.nib/info.nib | 50 + Carbon/Resources/ResKnife.nib/objects.xib | 1023 ++++++++ Carbon/Resources/ResKnife.r | 1 + Carbon/Resources/ResKnife.rsrc | Bin 0 -> 150438 bytes Carbon/Transfer.h | 35 + Cocoa/Classes/ApplicationDelegate.h | 14 + Cocoa/Classes/ApplicationDelegate.m | 91 + Cocoa/Classes/AttributesFormatter.h | 13 + Cocoa/Classes/AttributesFormatter.m | 65 + Cocoa/Classes/CreateResourceSheetController.h | 21 + Cocoa/Classes/CreateResourceSheetController.m | 32 + Cocoa/Classes/InfoWindow.h | 6 + Cocoa/Classes/InfoWindow.m | 15 + Cocoa/Classes/InfoWindowController.h | 34 + Cocoa/Classes/InfoWindowController.m | 102 + Cocoa/Classes/NameFormatter.h | 13 + Cocoa/Classes/NameFormatter.m | 41 + Cocoa/Classes/OutlineViewDelegate.h | 13 + Cocoa/Classes/OutlineViewDelegate.m | 20 + Cocoa/Classes/PrefsWindowController.h | 22 + Cocoa/Classes/PrefsWindowController.m | 93 + Cocoa/Classes/Resource.h | 44 + Cocoa/Classes/Resource.m | 155 ++ Cocoa/Classes/ResourceDataSource.h | 23 + Cocoa/Classes/ResourceDataSource.m | 91 + Cocoa/Classes/ResourceDocument.h | 21 + Cocoa/Classes/ResourceDocument.m | 256 ++ Cocoa/Classes/SizeFormatter.h | 9 + Cocoa/Classes/SizeFormatter.m | 45 + .../English.lproj/AboutPanel.nib/classes.nib | 4 + Cocoa/English.lproj/AboutPanel.nib/info.nib | 16 + .../English.lproj/AboutPanel.nib/objects.nib | Bin 0 -> 1996 bytes .../English.lproj/Application.nib/classes.nib | 29 + Cocoa/English.lproj/Application.nib/info.nib | 12 + .../English.lproj/Application.nib/objects.nib | Bin 0 -> 6186 bytes Cocoa/English.lproj/InfoPlist.strings | 6 + .../English.lproj/InfoWindow.nib/classes.nib | 20 + Cocoa/English.lproj/InfoWindow.nib/info.nib | 18 + .../English.lproj/InfoWindow.nib/objects.nib | Bin 0 -> 2490 bytes Cocoa/English.lproj/Localizable.strings | 5 + .../English.lproj/PrefsWindow.nib/classes.nib | 13 + Cocoa/English.lproj/PrefsWindow.nib/info.nib | 18 + .../English.lproj/PrefsWindow.nib/objects.nib | Bin 0 -> 2490 bytes .../ResourceDocument.nib/classes.nib | 53 + .../ResourceDocument.nib/info.nib | 27 + .../ResourceDocument.nib/objects.nib | Bin 0 -> 6842 bytes Cocoa/Resources/Icon file.icns | Bin 0 -> 6918 bytes Cocoa/Resources/ResKnife.icns | Bin 0 -> 39176 bytes Cocoa/Resources/Resource file.icns | Bin 0 -> 6918 bytes Cocoa/Resources/defaults.plist | 6 + Cocoa/main.c | 6 + Hex Editor/Classes/Events.cpp | 998 ++++++++ Hex Editor/Classes/Events.h | 1 + Hex Editor/Classes/HexWindow.cpp | 409 ++++ Hex Editor/Classes/HexWindow.h | 1 + Hex Editor/Classes/Initalisation.cpp | 1 + Hex Editor/Classes/Initalisation.h | 1 + Hex Editor/Classes/Utility.cpp | 233 ++ Hex Editor/Classes/Utility.h | 31 + Hex Editor/Hex Editor.h | 149 ++ PICT Editor/Classes/Initalisation.cpp | 1 + PICT Editor/Classes/Initalisation.h | 1 + PICT Editor/Classes/Parser.h | 0 PICT Editor/Classes/PictWindow.cpp | 7 + PICT Editor/PICT Editor.h | 1 + Prefix Files/Carbon Prefix.h | 1 + Prefix Files/Classic Prefix.h | 1 + ResKnife.mcp | Bin 0 -> 291585 bytes ResKnife.pbproj/project.pbxproj | 2133 +++++++++++++++++ Template Editor/Classes/Initalisation.cpp | 1 + Template Editor/Classes/Initalisation.h | 1 + Template Editor/Classes/TemplateWindow.cpp | 1 + Template Editor/Classes/TemplateWindow.h | 48 + Template Editor/Template Editor.h | 1 + Template Editor/Template Editor.r | 30 + 111 files changed, 16189 insertions(+) create mode 100644 Bug List.text create mode 100644 Carbon/Classes/Application.cpp create mode 100644 Carbon/Classes/Application.h create mode 100644 Carbon/Classes/Asynchronous.cpp create mode 100644 Carbon/Classes/Asynchronous.h create mode 100644 Carbon/Classes/DataBrowser.cpp create mode 100644 Carbon/Classes/DataBrowser.h create mode 100644 Carbon/Classes/EditorWindow.cpp create mode 100644 Carbon/Classes/EditorWindow.h create mode 100644 Carbon/Classes/Errors.cpp create mode 100644 Carbon/Classes/Errors.h create mode 100644 Carbon/Classes/FileWindow.cpp create mode 100644 Carbon/Classes/FileWindow.h create mode 100644 Carbon/Classes/Files.cpp create mode 100644 Carbon/Classes/Files.h create mode 100644 Carbon/Classes/HostCallbacks.cpp create mode 100644 Carbon/Classes/HostCallbacks.h create mode 100644 Carbon/Classes/InspectorWindow.cpp create mode 100644 Carbon/Classes/InspectorWindow.h create mode 100644 Carbon/Classes/PickerWindow.cpp create mode 100644 Carbon/Classes/PickerWindow.h create mode 100644 Carbon/Classes/PlugObject.cpp create mode 100644 Carbon/Classes/PlugObject.h create mode 100644 Carbon/Classes/PlugWindow.cpp create mode 100644 Carbon/Classes/PlugWindow.h create mode 100644 Carbon/Classes/ResourceObject.cpp create mode 100644 Carbon/Classes/ResourceObject.h create mode 100644 Carbon/Classes/Utility.cpp create mode 100644 Carbon/Classes/Utility.h create mode 100644 Carbon/Classes/WindowObject.cpp create mode 100644 Carbon/Classes/WindowObject.h create mode 100644 Carbon/Generic.h create mode 100644 Carbon/ResKnife.h create mode 100755 Carbon/ResKnife.lib create mode 100644 Carbon/Resources/Carbon.r create mode 100644 Carbon/Resources/ResKnife.nib/classes.nib create mode 100644 Carbon/Resources/ResKnife.nib/info.nib create mode 100644 Carbon/Resources/ResKnife.nib/objects.xib create mode 100644 Carbon/Resources/ResKnife.r create mode 100644 Carbon/Resources/ResKnife.rsrc create mode 100644 Carbon/Transfer.h create mode 100644 Cocoa/Classes/ApplicationDelegate.h create mode 100644 Cocoa/Classes/ApplicationDelegate.m create mode 100644 Cocoa/Classes/AttributesFormatter.h create mode 100644 Cocoa/Classes/AttributesFormatter.m create mode 100644 Cocoa/Classes/CreateResourceSheetController.h create mode 100644 Cocoa/Classes/CreateResourceSheetController.m create mode 100644 Cocoa/Classes/InfoWindow.h create mode 100644 Cocoa/Classes/InfoWindow.m create mode 100644 Cocoa/Classes/InfoWindowController.h create mode 100644 Cocoa/Classes/InfoWindowController.m create mode 100644 Cocoa/Classes/NameFormatter.h create mode 100644 Cocoa/Classes/NameFormatter.m create mode 100644 Cocoa/Classes/OutlineViewDelegate.h create mode 100644 Cocoa/Classes/OutlineViewDelegate.m create mode 100644 Cocoa/Classes/PrefsWindowController.h create mode 100644 Cocoa/Classes/PrefsWindowController.m create mode 100644 Cocoa/Classes/Resource.h create mode 100644 Cocoa/Classes/Resource.m create mode 100644 Cocoa/Classes/ResourceDataSource.h create mode 100644 Cocoa/Classes/ResourceDataSource.m create mode 100644 Cocoa/Classes/ResourceDocument.h create mode 100644 Cocoa/Classes/ResourceDocument.m create mode 100644 Cocoa/Classes/SizeFormatter.h create mode 100644 Cocoa/Classes/SizeFormatter.m create mode 100644 Cocoa/English.lproj/AboutPanel.nib/classes.nib create mode 100644 Cocoa/English.lproj/AboutPanel.nib/info.nib create mode 100644 Cocoa/English.lproj/AboutPanel.nib/objects.nib create mode 100644 Cocoa/English.lproj/Application.nib/classes.nib create mode 100644 Cocoa/English.lproj/Application.nib/info.nib create mode 100644 Cocoa/English.lproj/Application.nib/objects.nib create mode 100644 Cocoa/English.lproj/InfoPlist.strings create mode 100644 Cocoa/English.lproj/InfoWindow.nib/classes.nib create mode 100644 Cocoa/English.lproj/InfoWindow.nib/info.nib create mode 100644 Cocoa/English.lproj/InfoWindow.nib/objects.nib create mode 100644 Cocoa/English.lproj/Localizable.strings create mode 100644 Cocoa/English.lproj/PrefsWindow.nib/classes.nib create mode 100644 Cocoa/English.lproj/PrefsWindow.nib/info.nib create mode 100644 Cocoa/English.lproj/PrefsWindow.nib/objects.nib create mode 100644 Cocoa/English.lproj/ResourceDocument.nib/classes.nib create mode 100644 Cocoa/English.lproj/ResourceDocument.nib/info.nib create mode 100644 Cocoa/English.lproj/ResourceDocument.nib/objects.nib create mode 100644 Cocoa/Resources/Icon file.icns create mode 100644 Cocoa/Resources/ResKnife.icns create mode 100644 Cocoa/Resources/Resource file.icns create mode 100644 Cocoa/Resources/defaults.plist create mode 100644 Cocoa/main.c create mode 100644 Hex Editor/Classes/Events.cpp create mode 100644 Hex Editor/Classes/Events.h create mode 100644 Hex Editor/Classes/HexWindow.cpp create mode 100644 Hex Editor/Classes/HexWindow.h create mode 100644 Hex Editor/Classes/Initalisation.cpp create mode 100644 Hex Editor/Classes/Initalisation.h create mode 100644 Hex Editor/Classes/Utility.cpp create mode 100644 Hex Editor/Classes/Utility.h create mode 100644 Hex Editor/Hex Editor.h create mode 100644 PICT Editor/Classes/Initalisation.cpp create mode 100644 PICT Editor/Classes/Initalisation.h create mode 100644 PICT Editor/Classes/Parser.h create mode 100644 PICT Editor/Classes/PictWindow.cpp create mode 100644 PICT Editor/PICT Editor.h create mode 100644 Prefix Files/Carbon Prefix.h create mode 100644 Prefix Files/Classic Prefix.h create mode 100644 ResKnife.mcp create mode 100644 ResKnife.pbproj/project.pbxproj create mode 100644 Template Editor/Classes/Initalisation.cpp create mode 100644 Template Editor/Classes/Initalisation.h create mode 100644 Template Editor/Classes/TemplateWindow.cpp create mode 100644 Template Editor/Classes/TemplateWindow.h create mode 100644 Template Editor/Template Editor.h create mode 100644 Template Editor/Template Editor.r diff --git a/Bug List.text b/Bug List.text new file mode 100644 index 0000000..27ebe59 --- /dev/null +++ b/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 \ No newline at end of file diff --git a/Carbon/Classes/Application.cpp b/Carbon/Classes/Application.cpp new file mode 100644 index 0000000..b781e11 --- /dev/null +++ b/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, ¤tTab ); + SetControlVisibility( currentTab, selectedTab == id.id, true ); + id.id = 2; + GetControlByID( window, &id, ¤tTab ); + SetControlVisibility( currentTab, selectedTab == id.id, true ); + + return error; +} diff --git a/Carbon/Classes/Application.h b/Carbon/Classes/Application.h new file mode 100644 index 0000000..2b5fb50 --- /dev/null +++ b/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 \ No newline at end of file diff --git a/Carbon/Classes/Asynchronous.cpp b/Carbon/Classes/Asynchronous.cpp new file mode 100644 index 0000000..1844645 --- /dev/null +++ b/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 +// the sound. This is the only offically sanctioned universal method of pausing any +// sound. +// +// If the sound was successfully paused, the output record's paused flag is set. +// +//======================================================================================= +pascal OSErr SHPlayPause( long refNum ) +{ + SHOutPtr outRec; + SndCommand cmd; + OSErr error; + + outRec = SHOutRecFromRefNum(refNum ); + if( outRec != nil ) + { + // Don't bother with this if we're already paused. + if( outRec->paused ) + return kSHErrAlreadyPaused; + + // Get the current playback rate for this sound. +/* cmd.cmd = getRateCmd; + cmd.param1 = 0; + cmd.param2 = (long) &outRec->rate; + error = SndDoImmediate( &outRec->channel, &cmd ); + if( error != noErr ) + return error; +*/ + // Now pause with either a rateCmd or a pauseCmd, as appropriate + cmd.param1 = 0; + cmd.param2 = 0; +/* if( outRec->rate != 0 ) + { + // If we get something non-zero, it's safe to assume that whatever + // synthesizer we're talking to will be able to understand a rateCmd to + // restore the rate (probably the sampled synthesizer). To pause the + // sound, we'll set the rate to zero. + cmd.cmd = rateCmd; + error = SndDoImmediate( &outRec->channel, &cmd ); + if( error != noErr ) + return error; + } + else + { +*/ // This synthesizer doesn't understand rateCmds. So instead we'll just + // pause command queue processing with a pauseCmd. This is how command-type + // sounds (e.g. Simple Beep) are paused. + cmd.cmd = pauseCmd; + error = SndDoImmediate( &outRec->channel, &cmd ); + if( error != noErr ) + return error; +/* } +*/ + outRec->paused = true; + return error; + } + else return kSHErrBadRefNum; +} + +//======================================================================================= +// +// pascal OSErr SHPlayContinue( long refNum ) +// +// Summary: +// This routine continues playback of a previously paused sound. +// +// Scope: +// Public. +// +// Parameters: +// refNum The refNum of the sound the caller wants playback continued on. This +// should be the refNum of a sound that was previously paused with +// SHPlayPause. +// +// Returns: +// OSErr If the SndDoImmediate fails. +// kSHErrBadRefNum If the refNum doesn't refer to any current output record. +// kSHErrAlreadyContinued If the sound is not paused. +// +// Operation: +// First SHPlayContinue gets a pointer to the output record (if any) that refNum +// refers to. If found, and that sound is paused, we check the output record's +// rate field. If non-zero, then the sound was paused with rateCmd, so we send +// another rateCmd to restore its playback rate. Otherwise, we send a resumeCmd +// (to resume command-queue processing). (See the comments for SHPlayPause for +// details on the two methods of pausing sound.) If the resumeCmd is successful, +// we clear the output record's paused flag. +// +//======================================================================================= +pascal OSErr SHPlayContinue( long refNum ) +{ + SHOutPtr outRec; + SndCommand cmd; + OSErr error; + + outRec = SHOutRecFromRefNum(refNum ); + if( outRec != nil ) + { + // Don't even bother with this stuff if the channel isn't paused. + if( !outRec->paused ) + return kSHErrAlreadyContinued; + + // Now continue playback with a rateCmd or a resumeCmd, as appropriate. + cmd.param1 = 0; +/* if( outRec->rate != 0 ) + { + // Resume sampled sound playback by restoring the synthesizer's playback + // rate with a rateCmd. + cmd.cmd = rateCmd; + cmd.param2 = outRec->rate; + error = SndDoImmediate( &outRec->channel, &cmd ); + if( error != noErr ) + return error; + } + else + { +*/ // Resume sound queue processing with a resumeCmd. + cmd.cmd = resumeCmd; + cmd.param2 = 0; + error = SndDoImmediate( &outRec->channel, &cmd ); + if( error != noErr ) + return error; +/* } +*/ + outRec->paused = false; + return error; + } + else return kSHErrBadRefNum; +} + +//======================================================================================= +// +// pascal SHPlayStat SHPlayStatus( long refNum ) +// +// Summary: +// This routine returns a status value for the sound associated with refNum. +// +// Scope: +// Public. +// +// Parameters: +// refNum The sound for which the caller wishes status information. +// +// Returns: +// shpError = -1 If refNum has never been used (and is therefore invalid). +// shpFinished = 0 If the sound associated with refNum has completed. +// shpPaused = 1 If the sound associated with refNum is currently paused. +// shpPlaying = 2 If the sound associated with refNum is currently playing. +// +// Operation: +// First we check to see if refNum is greater than or equal to our next output +// reference number. If it is, then this reference number is definitely invalid, +// so we return shpError. Otherwise, we look refNum up with SHOutRecFromRefNum. +// If no record is found (but we know that refNum has been used in the past), we +// can assume that the sound has completed, and return shpFinished. Otherwise, +// the sound is currently playing or is paused, so we return either shpPaused or +// shpPlaying based on the value of the output record's paused flag. +// +//======================================================================================= +pascal SHPlayStat SHPlayStatus( long refNum ) +{ + SHOutPtr outRec; + + if( refNum >= gsSHOutVars.nextRef ) + return shpError; + else + { + outRec = SHOutRecFromRefNum( refNum ); + + if( outRec != nil ) + { + // We found an SHOutRec for the guy's ref num, (so it's in use). + return outRec->paused? shpPaused:shpPlaying; + } + else + { + // Although we've used the reference number in the past, it's not in use, so + // we can assume whatever sound it was associated has since stopped. So, + // we'll return shpFinished in this case. + return shpFinished; + } + } +} + +//======================================================================================= +// +// pascal OSErr SHGetChannel( long refNum, SndChannelPtr *channel ) +// +// Summary: +// This routine allows the caller to retrieve a pointer to the sound channel that +// is associated with the given refNum. +// +// Scope: +// Public. +// +// Parameters: +// refNum The sound for which the caller wants to retrieve a sound channel +// pointer. +// channel A pointer to a SndChannelPtr. This VAR parameter is where the sound +// channel address is stored if it is found. +// +// Returns: +// kSHErrBadRefNum If refNum doesn't refer to any current output record. +// noErr Otherwise. +// +// Operation: +// This routine is provided to allow more advanced callers to have access to the +// sound channel associated with a reference number. This could be useful, for +// instance, if the caller wanted to send sound commands to the channel, and only +// use the Helper to manage the channel (but not the sound). A good example of +// this is continuous background music. You make a sound with a soundCmd and a +// loop. Then you open a channel by doing a SHPlayByHandle(nil, &ref), then get +// the channel pointer by calling SHGetChannel, manually PlaySnd the background +// music sound (which should contain a soundCmd to install the music as a voice), +// then send a freqCmd to start the music playing. It'll keep looping until a +// quietCmd comes along. (SEE NOTE BELOW. ) +// +// IMPORTANT: +// If you use the above-described technique to provide looped background sound, it +// is important to note that when you change the background music (e.g. from one +// song to the next), you should SHPlayStop the channel, and allocate a new channel +// with new calls to SHPlayByHandle(nil, &ref)/SHGetChannel. DO NOT make another +// SndPlay call on the same channel to change the sound, because this will crash on +// pre-7.0 Systems. +// +//======================================================================================= +pascal OSErr SHGetChannel( long refNum, SndChannelPtr *channel ) +{ + SHOutPtr outRec; + + // Look for the output record associated with refNum. + outRec = SHOutRecFromRefNum( refNum ); + + // If we found one, return a pointer to the sound channel. + if( outRec != nil ) + { + *channel = &outRec->channel; + return noErr; + } + else return kSHErrBadRefNum; +} + +//======================================================================================= +// +// OSErr SHGetDeviceSettings( long inRefNum, short *numChannels, Fixed *sampleRate, short *sampleSize, OSType *compType ) +// +// Summary: +// This routine gets several parameters from an open sound input device. +// +// Scope: +// Private. +// +// Parameters: +// inRefNum The sound input device's input reference number. +// numChannels A VAR parameter in which the number of channels is returned. +// sampleRate A VAR parameter in which the sample rate (in Hz) is returned. +// sampleSize A VAR parameter in which the number of bits/sample is returned. +// compType A VAR parameter in which the compression type is returned. +// +// Returns: +// OSErr If any of the SPBGetDeviceInfo calls fail. +// noErr Otherwise. +// +// Operation: +// This routine does four SPBGetDeviceInfo calls to retrieve the number of channels +// the sample rate, the sample size, and the compression type for the input device +// referrored to by inRefNum. This routine is almost verbatim out of Inside Macintosh +// Volume 6. +// +//======================================================================================= +OSErr SHGetDeviceSettings( long inRefNum, short *numChannels, Fixed *sampleRate, short *sampleSize, OSType *compType ) +{ + OSErr error = noErr; + + // Hit on that sound input device. + error = SPBGetDeviceInfo( inRefNum, siNumberChannels, (Ptr) numChannels ); + if( error != noErr ) return error; + error = SPBGetDeviceInfo( inRefNum, siSampleRate, (Ptr) sampleRate ); + if( error != noErr ) return error; + error = SPBGetDeviceInfo(inRefNum, siSampleSize, (Ptr) sampleSize ); + if( error != noErr ) return error; + error = SPBGetDeviceInfo(inRefNum, siCompressionType, (Ptr) compType ); + return error; +} + +//======================================================================================= +// +// pascal OSErr SHRecordStart( short maxK, OSType quality, Boolean *doneFlag ) +// +// Summary: +// This routine initiates asynchronous sound recording. +// +// Scope: +// Public. +// +// Parameters: +// maxK The amount of memory (in 1024-byte chunks) the caller wishes to +// preallocate for the user to record into. +// quality One of the standard Macintosh Sound Input Manager qualities: 'good', +// 'betr', or 'best'. +// doneFlag A pointer to a Boolean by which the Helper will inform the caller +// that recording has finished and an SHGetRecordedSound call is in +// order. If nil, then the caller will not be directly informed when +// recording is complete, but will instead have to call SHRecordStatus +// to find out. +// +// Returns: +// OSErr If any of the stages fail. +// noErr Otherwise. +// +// Operation: +// This routine initiates asynchronous recording. There are eight stages, each +// of which could fail. So, each state checks that the previous stage was success- +// ful, so errors fall through the bottom. Along the way, the local bools +// deviceOpened and allocated are set when the sound input device is opened and +// the sound input buffer is allocated, respectively. If one or more of these flags +// is set at the end of the routine AND there's been an error, then device closure/ +// deallocation is performed as required. The stages are as follows: +// +// 1. Open the sound input device. +// 2. Ask the device if it can do asynchronous recording. +// 3. Allocate the sound input buffer, as specified by the maxK parameter. +// 4. Turn on metering and set the recording quality as specified by the +// quality parameter. +// 5. The fifth stage is to grab (and save inside the gsInVars structure) the +// default number of channels, sample rate, sample size, and compression +// type. We'll use the saved values later when we go to install a more +// accurate header into the sound, at completion time. +// 6. Create a sound header in the sound input buffer. +// 7. Fill out the sound input parameter block inPB. Note that we put the +// application A5 into the userLong field of the parameter block, so that +// SHRecordCompletion can access our globals. Also note that +// SHRecordCompletion is installed directly as the callback routine. This +// stage cannot fail, and sets error to noErr. +// 8. The eighth and final stage is to set various flags such that we know +// we're recording, and calls SPBRecord to initiate the asynchronous +// recording process. The reason we set our flags before we make the +// SPBRecord call is to avoid a "race" condition, where the recording could +// (theoretically) complete virtually immediately and our callback routine +// would get called before the flags were set up, thus confusing it. To +// avoid this, set the flags first, then the error handler code can reset +// them if the recording failed. +// +// If an error occurred along the way, the sound input device may be closed, and/or +// the sound input buffer may be deallocated. +// +//======================================================================================= +pascal OSErr SHRecordStart( short maxK, OSType quality, Boolean *doneFlag ) +{ + Boolean deviceOpened = false; + Boolean allocated = false; + + OSErr error = noErr; + short canDoAsync; + short metering; + long allocSize; + + // 1. Try to open the current sound input device + error = SPBOpenDevice( nil, siWritePermission, &gsSHInVars.inRefNum ); + if( error == noErr ) + deviceOpened = true; + + // 2. Now let's see if this device can even handle asynchronous recording. + if( error == noErr ) + { + error = SPBGetDeviceInfo( gsSHInVars.inRefNum, siAsync, (Ptr) &canDoAsync ); + if( error == noErr && !canDoAsync ) + error = kSHErrNonAsychDevice; + } + + // 3. Try to allocate memory for the guy's sound. + if( error == noErr ) + { + allocSize = (maxK * 1024) + kSHHeaderSlop; + gsSHInVars.inHandle = NewHandle(allocSize ); + if( gsSHInVars.inHandle == nil ) + { + error = MemError(); + if( error == noErr ) + error = memFullErr; + } + if( error == noErr ) + allocated = true; + } + + // 4. Set up various recording parameters (metering and quality ) + if( error == noErr ) + { + metering = 1; + SPBSetDeviceInfo( gsSHInVars.inRefNum, siLevelMeterOnOff, (Ptr) &metering ); + error = SPBSetDeviceInfo( gsSHInVars.inRefNum, siRecordingQuality, (Ptr) &quality ); + } + + // 5. Call SHGetDeviceSettings to determine a bunch of information we'll need to + // make a header for this sound. + if( error == noErr ) + { + error = SHGetDeviceSettings(gsSHInVars.inRefNum, &gsSHInVars.numChannels, + &gsSHInVars.sampleRate, &gsSHInVars.sampleSize, &gsSHInVars.compType ); + } + + // 6. Create a header for this sound. + if( error == noErr ) + { + error = SetupSndHeader((SndListHandle)gsSHInVars.inHandle, gsSHInVars.numChannels, gsSHInVars.sampleRate, gsSHInVars.sampleSize, + gsSHInVars.compType, kSHBaseNote, allocSize, &gsSHInVars.headerLength ); + } + + // 7. Lock the input sound handle and set up the input parameter block. + if( error == noErr ) + { + MoveHHi( gsSHInVars.inHandle ); + HLock( gsSHInVars.inHandle ); + + allocSize -= gsSHInVars.headerLength; + gsSHInVars.inPB.inRefNum = gsSHInVars.inRefNum; + gsSHInVars.inPB.count = allocSize; + gsSHInVars.inPB.milliseconds = 0; + gsSHInVars.inPB.bufferLength = allocSize; + gsSHInVars.inPB.bufferPtr = *gsSHInVars.inHandle + gsSHInVars.headerLength; + gsSHInVars.inPB.completionRoutine = gsSHRecordCompletionUPP; + gsSHInVars.inPB.interruptRoutine = nil; + gsSHInVars.inPB.userLong = 0L; + gsSHInVars.inPB.error = noErr; + gsSHInVars.inPB.unused1 = 0; + + error = noErr; + } + + // 8. Finally, if all went well, set our recording flag, make sure our record + // completion flag is clear, and initiate asychronous recording. + if( error == noErr ) + { + gsSHInVars.recording = true; + gsSHInVars.recordComplete = false; + gsSHInVars.appComplete = doneFlag; + gsSHInVars.paused = false; + if( gsSHInVars.appComplete != nil ) + *gsSHInVars.appComplete = false; + + error = SPBRecord(&gsSHInVars.inPB, kSHAsync ); + } + + // Now clean up any errors that might have occurred. + if( error != noErr ) + { + gsSHInVars.recording = false; + if( deviceOpened ) + SPBCloseDevice( gsSHInVars.inRefNum ); + if( allocated ) + { + DisposeHandle( gsSHInVars.inHandle ); + gsSHInVars.inHandle = nil; + } + } + + return error; +} + +//======================================================================================= +// +// pascal OSErr SHGetRecordedSound( Handle *theSound ) +// +// Summary: +// This routine returns the sound handle from the last sound the Helper recorded. +// +// Scope: +// Public. +// +// Parameters: +// theSound A pointer to a Handle by which to return the sound handle. +// +// Returns: +// kSHErrNoRecording If there IS NO "last recorded sound" +// OSErr If recording stopped because of an error +// noErr Otherwise. +// +// Operation: +// This routine returns the sound handle (stored in the inHandle field of the +// gsSHInVars struct) from the last sound that was recorded by the Helper. If +// the recording died by an error (other than abortErr), then the error is returned +// and *theSound is set to nil. SHGetRecordedSound is the method by which you +// retrieve a sound handle, once you know (by whatever means) that recording is +// complete. +// +//======================================================================================= +pascal OSErr SHGetRecordedSound( Handle *theSound ) +{ + if( gsSHInVars.recordComplete ) + { + if( gsSHInVars.recordErr != noErr ) + { + *theSound = nil; + return gsSHInVars.recordErr; + } + else + { + *theSound = gsSHInVars.inHandle; + return noErr; + } + } + else + { + *theSound = nil; + return kSHErrNoRecording; + } +} + +//======================================================================================= +// +// pascal OSErr SHRecordStop( void ) +// +// Summary: +// This routine immediately stops sound recording. +// +// Scope: +// Public. +// +// Parameters: +// None. +// +// Returns: +// OSErr Whatever SPBStopRecording returns. +// +// Operation: +// This routine simply calls SPBStopRecording to immediately stop sound recording. +// It sets the parameter block's error code to abortErr, and calls the callback +// routine. Use this routine to implement a "stop" button. +// +//======================================================================================= +pascal OSErr SHRecordStop( void ) +{ + if( gsSHInVars.recording ) + return SPBStopRecording(gsSHInVars.inRefNum); + return noErr; +} + +//======================================================================================= +// +// pascal OSErr SHRecordPause( void ) +// +// Summary: +// This routine pauses sound recording. +// +// Scope: +// Public. +// +// Parameters: +// None. +// +// Returns: +// kSHErrNotRecording If we're not recording right now. +// kSHErrAlreadyPaused If recording is already paused. +// OSErr Whatever SPBPauseRecording returns. +// +// Operation: +// This routine pauses sound recording if we ARE recording and we're not already +// paused. Use this routine to implement a "pause" button. +// +//======================================================================================= +pascal OSErr SHRecordPause( void ) +{ + OSErr error = noErr; + if( gsSHInVars.recording ) + { + if( !gsSHInVars.paused ) + { + error = SPBPauseRecording( gsSHInVars.inRefNum ); + gsSHInVars.paused = (error == noErr); + return error; + } + else return kSHErrAlreadyPaused; + } + else return kSHErrNotRecording; +} + +//======================================================================================= +// +// pascal OSErr SHRecordContinue( void ) +// +// Summary: +// This routine resumes recording when recording has previously been paused. +// +// Scope: +// Public. +// +// Parameters: +// None. +// +// Returns: +// kSHErrNotRecording If we're not recording right now. +// kSHErrAlreadyContinued If we're not paused. +// OSErr Whatever SPBResumeRecording returns. +// +// Operation: +// This routine continues sound recording if we ARE recording and we've been +// previously paused. Use this routine to implement a "unpause" button. +// +//======================================================================================= +pascal OSErr SHRecordContinue( void ) +{ + OSErr error = noErr; + + if( gsSHInVars.recording ) + { + if( gsSHInVars.paused ) + { + error = SPBResumeRecording( gsSHInVars.inRefNum ); + gsSHInVars.paused = !(error == noErr); + return error; + } + else return kSHErrAlreadyContinued; + } + else return kSHErrNotRecording; +} + +//======================================================================================= +// +// pascal OSErr SHRecordStatus( SHRecordStatusRec *recordStatus ) +// +// Summary: +// This routine returns status information on sound that is being recorded. +// +// Scope: +// Public. +// +// Parameters: +// recordStatus A pointer to a SHRecordStatusRec into which to store the +// recording status. +// +// Returns: +// kSHErrNotRecording If we're not recording right now. +// OSErr Whatever SPBGetRecordingStatus returns. +// noErr Otherwise. +// +// Operation: +// If we're currently recording, we call SPBGetRecordingStatus. It tells us lots +// of handy things, most of which we return via recordStatus, then we give a status +// of either shrPaused or shrRecording, depending upon the state of the +// gsSHInVars.paused flag. If recording is complete, we return shrFinished. If an +// error occurs, we give a status of shrError. +// +//======================================================================================= +pascal OSErr SHRecordStatus( SHRecordStatusRec *recordStatus ) +{ + short recStatus; + OSErr error = noErr; + unsigned long totalSamplesToRecord,numberOfSamplesRecorded; + + if( gsSHInVars.recording ) + { + error = SPBGetRecordingStatus( gsSHInVars.inRefNum, &recStatus, + &recordStatus->meterLevel, &totalSamplesToRecord, &numberOfSamplesRecorded, + &recordStatus->totalRecordTime, &recordStatus->currentRecordTime ); + if( error == noErr ) + recordStatus->recordStatus = ( gsSHInVars.paused? shrPaused:shrRecording ); + else recordStatus->recordStatus = shrError; + return error; + } + else if( gsSHInVars.recordComplete ) + { + recordStatus->recordStatus = shrFinished; + recordStatus->meterLevel = 0; + // Don't know about the other fields -- just leave 'em + return error; + } + else return kSHErrNotRecording; +} diff --git a/Carbon/Classes/Asynchronous.h b/Carbon/Classes/Asynchronous.h new file mode 100644 index 0000000..74af642 --- /dev/null +++ b/Carbon/Classes/Asynchronous.h @@ -0,0 +1,121 @@ +#if defined(__MWERKS__) + #include + #include +#else + #include +#endif + +// enumerations +typedef enum +{ + shpError = -1, + shpFinished = 0, + shpPaused = 1, + shpPlaying = 2 +} SHPlayStat; + +typedef enum +{ + shrError = -1, + shrFinished = 0, + shrPaused = 1, + shrRecording = 2 +} SHRecordStat; + +// Sound Helper error codes +enum +{ + kSHErrOutaChannels = 1, // No more output records are available + kSHErrBadRefNum, // Invalid reference number + kSHErrNonAsychDevice, // Input device can't handle asynchronous input + kSHErrNoRecording, // There's no recording to return + kSHErrNotRecording, // Not allowed because we're not recording + kSHErrAlreadyPaused, // Already paused + kSHErrAlreadyContinued // Already continued +}; + +// Contants used by the Asynchronous Sound Helper +const SInt16 kSHDefChannels = 4; // Default number of channels to preallocate +const SInt16 kSHCompleteSig = 'SH'; // Flag we use to know a "true" completion callback +const SInt32 kSHComplete = 'SHcp'; // Flag that a given channel has completed playback +const SInt16 kSHHeaderSlop = 100; // Extra bytes for the sound header when recording +const SInt16 kSHBaseNote = 60; // Middle C base note for new recordings +const SInt16 kSHSyncWaitTimeout = 60; // Ticks to sync-wait when killing the Helper + +// Constants that should be in Sound.h but aren't +const SInt16 kSHNoSynth = 0; // Don't associate any synth to this channel +const SInt16 kSHNoInit = 0; // No specific initialization +const SInt8 kSHQuietNow = true; // Stop playing this sound immediately +const SInt8 kSHAsync = true; // Play asynchronously +const SInt8 kSHWait = false; // Wait for there to be enough room in the queue + +// structures +typedef struct +{ + SHRecordStat recordStatus; // Current record status + unsigned long totalRecordTime; // Total (maximum) record time in ms + unsigned long currentRecordTime; // Current recorded time in ms + short meterLevel; // 0..255, the current input level +} SHRecordStatusRec; + +typedef struct +{ + SndChannel channel; // Our sound channel + long refNum; // Our Helper ref num + Handle sound; // The sound we're playing + Fixed rate; // The rate at which a sampled sound is playing + char handleState; // The handle state to restore this handle to + Boolean inUse; // Tells whether this SHOutRec is in use + Boolean paused; // Tells whether this sound is currently paused +} SHOutRec, *SHOutPtr; + +typedef struct +{ + short numOutRecs; // The number of output records in outArray + SHOutRec *outArray; // Our pre-allocated output records + long nextRef; // The next available output reference number +} SHOutputVars; + +typedef struct +{ + long inRefNum; // Sound Input Manager's device refNum + SPB inPB; // The input parameter block + Handle inHandle; // The handle we're recording into + short headerLength; // The length of the sound's header + Boolean recording; // Tells whether we're actually recording + Boolean recordComplete; // Tells whether recording is complete + OSErr recordErr; // Error, if error terminated recording + short numChannels; // Number of channels for recording + short sampleSize; // Sample size for recording + Fixed sampleRate; // Sample rate for recording + OSType compType; // Compression type for recording + Boolean *appComplete; // Flag to caller that recording is done + Boolean paused; // Tells whether recording has been paused +} SHInputVars; + +// Initialization, idle, and termination +pascal OSErr SHInitSoundHelper( Boolean *attnFlag, short numChannels ); +pascal void SHIdle( void ); +pascal void SHKillSoundHelper(void ); + +// Easy sound output +pascal OSErr SHPlayByID( short resID, long *refNum ); +pascal OSErr SHPlayByHandle( Handle sound, long *refNum ); +pascal OSErr SHPlayStop( long refNum ); +pascal OSErr SHPlayStopAll( void ); + +// Advanced sound output +pascal OSErr SHPlayPause( long refNum ); +pascal OSErr SHPlayContinue( long refNum ); +pascal SHPlayStat SHPlayStatus( long refNum ); +pascal OSErr SHGetChannel( long refNum, SndChannelPtr *channel ); + +// Easy sound input +pascal OSErr SHRecordStart( short maxK, OSType quality, Boolean *doneFlag ); +pascal OSErr SHGetRecordedSound( Handle *theSound ); +pascal OSErr SHRecordStop( void ); + +// Advanced sound input +pascal OSErr SHRecordPause( void ); +pascal OSErr SHRecordContinue( void ); +pascal OSErr SHRecordStatus( SHRecordStatusRec *recordStatus ); \ No newline at end of file diff --git a/Carbon/Classes/DataBrowser.cpp b/Carbon/Classes/DataBrowser.cpp new file mode 100644 index 0000000..2d983dd --- /dev/null +++ b/Carbon/Classes/DataBrowser.cpp @@ -0,0 +1,634 @@ +#include "DataBrowser.h" +#include "ResourceObject.h" +#include "InspectorWindow.h" +#include "Errors.h" +#include "Utility.h" // for TypeToCFString() et cetera + +extern globals g; + +#if TARGET_API_MAC_CARBON // CarbonLib 1.1+ or Public Beta only + +/*** INITALISE DATA BROWSER ***/ +OSStatus FileWindow::InitDataBrowser( void ) +{ + OSStatus error = noErr; + + // get the db control - compatable with both CarbonLib and nib based versions + ControlID id = { kDataBrowserSignature, 0 }; + GetControlByID( window, &id, &dataBrowser ); + + // set control ref to FileWindow + SetControlReference( dataBrowser, (long) this ); + + // turn off frame and focus + Boolean frame = false; + SetControlData( dataBrowser, kControlNoPart, kControlDataBrowserIncludesFrameAndFocusTag, sizeof(Boolean), &frame ); + +#if !USE_NIBS + // add empty columns + AddDataBrowserColumn( dataBrowser, kDBNameColumn, 0 ); // save column order into prefs file: Get/SetDataBrowserUserState() + AddDataBrowserColumn( dataBrowser, kDBTypeColumn, 1 ); + AddDataBrowserColumn( dataBrowser, kDBIDColumn, 2 ); + AddDataBrowserColumn( dataBrowser, kDBSizeColumn, 3 ); +#endif + + // add callbacks + DataBrowserCallbacks theCallbacks; + theCallbacks.version = kDataBrowserLatestCallbacks; + InitDataBrowserCallbacks( &theCallbacks ); + theCallbacks.u.v1.itemDataCallback = NewDataBrowserItemDataUPP( DataBrowserItemData ); + theCallbacks.u.v1.itemCompareCallback = NewDataBrowserItemCompareUPP( SortDataBrowser ); + theCallbacks.u.v1.itemNotificationCallback = NewDataBrowserItemNotificationUPP( DataBrowserMessage ); + theCallbacks.u.v1.addDragItemCallback = NewDataBrowserAddDragItemUPP( DataBrowserAddDragItem ); + theCallbacks.u.v1.acceptDragCallback = NewDataBrowserAcceptDragUPP( DataBrowserAcceptDrag ); + theCallbacks.u.v1.receiveDragCallback = NewDataBrowserReceiveDragUPP( DataBrowserReceiveDrag ); + theCallbacks.u.v1.postProcessDragCallback = NewDataBrowserPostProcessDragUPP( DataBrowserPostProcessDrag ); + SetDataBrowserCallbacks( dataBrowser, &theCallbacks ); + + // setup rest of browser, inc. adding all resources + DataBrowserItemID item; + for( UInt32 n = 1; n <= numResources; n++ ) + { + item = n; + error = AddDataBrowserItems( dataBrowser, kDataBrowserNoItem, 1, &item, kDataBrowserItemNoProperty ); + if( error ) DebugError( "\pError occoured adding resource to data browser." ); + } + + // add data fork if present + if( resourceMap->RepresentsDataFork() ) // requires data fork to be first in chain + { + item = kDataBrowserDataForkItem; // curently 0xFFFFFFFF + error = AddDataBrowserItems( dataBrowser, kDataBrowserNoItem, 1, &item, kDataBrowserItemNoProperty ); + if( error ) DebugError( "\pError occoured adding data fork to data browser." ); + } + + SetDataBrowserSortProperty( dataBrowser, kDBTypeColumn ); + SetDataBrowserTableViewRowHeight( dataBrowser, 16 +2 ); + SetDataBrowserListViewDisclosureColumn( dataBrowser, kDBNameColumn, true ); + + // set up drag tracking + SetControlDragTrackingEnabled( dataBrowser, true ); + return error; +} + +/*** ADD DATA BROWSER COLUMN ***/ +void AddDataBrowserColumn( ControlRef browser, DataBrowserPropertyID column, UInt16 position ) +{ + DataBrowserListViewColumnDesc columnDesc; + switch( column ) + { + case kDataBrowserNameColumn: + columnDesc.propertyDesc.propertyID = kDataBrowserNameColumn; + columnDesc.propertyDesc.propertyType = kDataBrowserIconAndTextType; + columnDesc.propertyDesc.propertyFlags = kDataBrowserListViewDefaultColumnFlags | kDataBrowserListViewSelectionColumn; + + columnDesc.headerBtnDesc.version = kDataBrowserListViewLatestHeaderDesc; + columnDesc.headerBtnDesc.minimumWidth = 150; + columnDesc.headerBtnDesc.maximumWidth = 250; + columnDesc.headerBtnDesc.titleOffset = 0; + columnDesc.headerBtnDesc.titleString = CFSTR("Resource Name"); // these should be resources for ease of localisation + columnDesc.headerBtnDesc.initialOrder = kDataBrowserOrderIncreasing; + columnDesc.headerBtnDesc.btnFontStyle.font = kControlFontViewSystemFont; + columnDesc.headerBtnDesc.btnFontStyle.just = teFlushDefault; + columnDesc.headerBtnDesc.btnFontStyle.style = normal; + + columnDesc.headerBtnDesc.btnContentInfo.contentType = kControlContentTextOnly; + break; + + case kDataBrowserTypeColumn: + columnDesc.propertyDesc.propertyID = kDataBrowserTypeColumn; + columnDesc.propertyDesc.propertyType = kDataBrowserTextType; + columnDesc.propertyDesc.propertyFlags = kDataBrowserListViewDefaultColumnFlags; + + columnDesc.headerBtnDesc.version = kDataBrowserListViewLatestHeaderDesc; + columnDesc.headerBtnDesc.minimumWidth = (g.systemVersion < kMacOSX)? 56:72; + columnDesc.headerBtnDesc.maximumWidth = columnDesc.headerBtnDesc.minimumWidth; + columnDesc.headerBtnDesc.titleOffset = 0; + columnDesc.headerBtnDesc.titleString = CFSTR("Type"); // these should be resources for ease of localisation + columnDesc.headerBtnDesc.initialOrder = kDataBrowserOrderIncreasing; + + columnDesc.headerBtnDesc.btnFontStyle.font = kControlFontViewSystemFont; + columnDesc.headerBtnDesc.btnFontStyle.just = teFlushRight; + columnDesc.headerBtnDesc.btnFontStyle.style = normal; + columnDesc.headerBtnDesc.btnContentInfo.contentType = kControlContentTextOnly; + break; + + case kDataBrowserIDColumn: + columnDesc.propertyDesc.propertyID = kDataBrowserIDColumn; + columnDesc.propertyDesc.propertyType = kDataBrowserTextType; + columnDesc.propertyDesc.propertyFlags = kDataBrowserListViewDefaultColumnFlags; + + columnDesc.headerBtnDesc.version = kDataBrowserListViewLatestHeaderDesc; + columnDesc.headerBtnDesc.minimumWidth = (g.systemVersion < kMacOSX)? 56:72; + columnDesc.headerBtnDesc.maximumWidth = columnDesc.headerBtnDesc.minimumWidth; + columnDesc.headerBtnDesc.titleOffset = 0; + columnDesc.headerBtnDesc.titleString = CFSTR("ID"); // these should be resources for ease of localisation + columnDesc.headerBtnDesc.initialOrder = kDataBrowserOrderIncreasing; + + columnDesc.headerBtnDesc.btnFontStyle.font = kControlFontViewSystemFont; + columnDesc.headerBtnDesc.btnFontStyle.just = teFlushRight; + columnDesc.headerBtnDesc.btnFontStyle.style = normal; + columnDesc.headerBtnDesc.btnContentInfo.contentType = kControlContentTextOnly; + break; + + case kDataBrowserSizeColumn: + columnDesc.propertyDesc.propertyID = kDataBrowserSizeColumn; + columnDesc.propertyDesc.propertyType = kDataBrowserTextType; + columnDesc.propertyDesc.propertyFlags = kDataBrowserListViewDefaultColumnFlags; + + columnDesc.headerBtnDesc.version = kDataBrowserListViewLatestHeaderDesc; + columnDesc.headerBtnDesc.minimumWidth = (g.systemVersion < kMacOSX)? 56:72; + columnDesc.headerBtnDesc.maximumWidth = columnDesc.headerBtnDesc.minimumWidth; + columnDesc.headerBtnDesc.titleOffset = 0; + columnDesc.headerBtnDesc.titleString = CFSTR("Size"); // these should be resources for ease of localisation + columnDesc.headerBtnDesc.initialOrder = kDataBrowserOrderIncreasing; + + columnDesc.headerBtnDesc.btnFontStyle.font = kControlFontViewSystemFont; + columnDesc.headerBtnDesc.btnFontStyle.just = teFlushRight; + columnDesc.headerBtnDesc.btnFontStyle.style = normal; + columnDesc.headerBtnDesc.btnContentInfo.contentType = kControlContentTextOnly; + break; + } + + // create column and make respond to sorting + AddDataBrowserListViewColumn( browser, &columnDesc, position ); +} + +/*** HANDLE ITEM DATA I/O ***/ +pascal OSStatus DataBrowserItemData( ControlRef browser, DataBrowserItemID itemID, DataBrowserPropertyID property, DataBrowserItemDataRef itemData, Boolean setValue ) +{ + #pragma unused( setValue ) + OSStatus result = noErr; + if( setValue ) return result; + FileWindowPtr file = (FileWindowPtr) GetControlReference( browser ); + ResourceObjectPtr resource = file->GetResource( itemID ); + + if( resource == null ) + DebugError( "\pNull resource returned within DataBrowserItemData()" ); + + switch( property ) + { + case kDataBrowserItemIsEditableProperty: + if( true ) // should item be editable? (i.e. is it a name, ID or type?) + SetDataBrowserItemDataBooleanValue( itemData, true ); + break; + + case kDataBrowserItemIsContainerProperty: + if( resource->Type() == kIconFamilyType ) + SetDataBrowserItemDataBooleanValue( itemData, true ); + break; + + case kDBNameColumn: + { // icon Ñ no resource for the icon! + IconRef theIcon = null; +#if !USE_NIBS + if( itemID != kDataBrowserDataForkItem ) + { + Str255 iconString; + TypeToPString( resource->Type(), iconString ); + IconFamilyHandle iconFamily = (IconFamilyHandle) Get1NamedResource( kIconFamilyType, iconString ); + if( iconFamily ) + { + RegisterIconRefFromIconFamily( kResKnifeCreator, resource->Type(), iconFamily, &theIcon ); + ReleaseResource( (Handle) iconFamily ); // when dragging a rect this call caused other columns not to be displayed !?! + } + } +#endif + if( theIcon == null ) + GetIconRef( kOnSystemDisk, kResKnifeCreator, kResourceFileType, &theIcon ); + SetDataBrowserItemDataIcon( itemData, theIcon ); + ReleaseIconRef( theIcon ); + + // resource name + CFStringRef nameCFStr; + if( itemID == kDataBrowserDataForkItem ) + { +#if USE_NIBS // OS 9 version is not bundled at the present time + nameCFStr = CFBundleCopyLocalizedString( CFBundleGetMainBundle(), CFSTR("Data Fork"), null, null ); // bug: doesn't actually get localized string! +#else + nameCFStr = CFSTR("Data Fork"); +#endif + SetDataBrowserItemDataRGBColor( itemData, &g.textColour ); + } + else if( *resource->Name() == 0x00 ) + { +#if USE_NIBS // OS 9 version is not bundled at the present time + nameCFStr = CFBundleCopyLocalizedString( CFBundleGetMainBundle(), CFSTR("Untitled Resource"), null, null ); // bug: doesn't actually get localized string! +#else + nameCFStr = CFSTR("Untitled Resource"); +#endif + SetDataBrowserItemDataRGBColor( itemData, &g.textColour ); + } + else nameCFStr = CFStringCreateWithPascalString( CFAllocatorGetDefault(), resource->Name(), kCFStringEncodingMacRoman ); + SetDataBrowserItemDataText( itemData, nameCFStr ); +#if USE_NIBS // OS 9 uses CFSTR() + CFRelease( nameCFStr ); +#endif + } break; + + case kDBTypeColumn: + { // resource type + if( itemID == kDataBrowserDataForkItem ) + { + SetDataBrowserItemDataText( itemData, CFSTR("-") ); + } + else + { + CFStringRef typeString; + TypeToCFString( resource->Type(), &typeString ); + SetDataBrowserItemDataText( itemData, typeString ); + CFRelease( typeString ); + } + } break; + + case kDBIDColumn: + { // resource ID + if( itemID == kDataBrowserDataForkItem ) + { + SetDataBrowserItemDataText( itemData, CFSTR("-") ); + } + else + { + SInt16 id = resource->ID(); + Str255 idPString; + NumToString( id, (StringPtr) &idPString ); + CFStringRef idString = CFStringCreateWithPascalString( CFAllocatorGetDefault(), idPString, kCFStringEncodingMacRoman ); + SetDataBrowserItemDataText( itemData, idString ); + CFRelease( idString ); + } + } break; + + case kDBSizeColumn: + { SInt32 size = resource->Size(); + UInt8 power = 0, remainder = 0; + Str255 sizePString, frac; + while( size >= 1024 && power <= 30 ) + { + power += 10; // 10 == KB, 20 == MB, 30 == GB + remainder = (UInt8) ((size % 1024) / 102.4); // 102.4 gives one dp, 10.24 would give two dps, 1.024 would give three dps + size /= 1024; + } + NumToString( (long) size, (StringPtr) &sizePString ); + NumToString( remainder, (StringPtr) &frac ); + if( power ) // some division has occoured + { + if( sizePString[0] < 3 && remainder > 0 ) + { + AppendPString( (unsigned char *) &sizePString, "\p." ); // bug: should be a comma on european systems + AppendPString( (unsigned char *) &sizePString, (unsigned char *) &frac ); + } + if( power == 10 ) AppendPString( (unsigned char *) &sizePString, "\p KB" ); + else if( power == 20 ) AppendPString( (unsigned char *) &sizePString, "\p MB" ); + else if( power == 30 ) AppendPString( (unsigned char *) &sizePString, "\p GB" ); // everything bigger will be given in GB + } + CFStringRef sizeString = CFStringCreateWithPascalString( CFAllocatorGetDefault(), sizePString, kCFStringEncodingMacRoman ); + SetDataBrowserItemDataText( itemData, sizeString ); + CFRelease( sizeString ); + } break; + + default: + result = errDataBrowserPropertyNotSupported; + break; + } + return result; +} + +/*** SORT DATA BROWSER ***/ +pascal Boolean SortDataBrowser( ControlRef browser, DataBrowserItemID itemOne, DataBrowserItemID itemTwo, DataBrowserPropertyID sortProperty ) +{ + short result; + Str255 typeOne, typeTwo; + StringPtr nameOne, nameTwo; + FileWindowPtr file = (FileWindowPtr) GetControlReference( browser ); + + // send data fork to top regardless of property + if( itemOne == kDataBrowserDataForkItem ) return true; + if( itemTwo == kDataBrowserDataForkItem ) return false; + + // validate data browser item IDs + if( itemOne <= kDataBrowserNoItem || itemOne > file->GetResourceCount() ) + { + DebugError( "\psort item one was invalid" ); + return false; + } + if( itemTwo <= kDataBrowserNoItem || itemTwo > file->GetResourceCount() ) + { + DebugError( "\psort item two was invalid" ); + return false; + } + + // get resource corrisponding to item ID + ResourceObjectPtr resourceOne = file->GetResource( itemOne ); + ResourceObjectPtr resourceTwo = file->GetResource( itemTwo ); + if( resourceOne == null || resourceTwo == null ) + DebugError( "\pNull resource returned within SortDataBrowser()" ); + + // sort resources according to property user has selected + switch( sortProperty ) + { + case kDBNameColumn: + nameOne = resourceOne->Name(); + nameTwo = resourceTwo->Name(); + result = CompareString( nameOne, nameTwo, null ); + return result < 0; + + case kDBTypeColumn: + TypeToPString( resourceOne->Type(), typeOne ); + TypeToPString( resourceTwo->Type(), typeTwo ); + result = CompareString( typeOne, typeTwo, null ); + return result < 0; + + case kDBIDColumn: + return resourceOne->ID() < resourceTwo->ID(); + + case kDBSizeColumn: + return resourceOne->Size() < resourceTwo->Size(); + + case kDataBrowserItemNoProperty: // this is valid when first constructing the data browser +// DebugError( "\pkDataBrowserItemNoProperty passed to sort function" ); + return false; + + default: + DebugError( "\pInvalid sort property given" ); + return false; + } + return false; +} + +/*** DATA BROWSER MESSAGE ***/ +pascal void DataBrowserMessage( ControlRef browser, DataBrowserItemID itemID, DataBrowserItemNotification message, DataBrowserItemDataRef itemData ) +{ + #pragma unused( itemID, itemData ) + FileWindowPtr file = (FileWindowPtr) GetControlReference( browser ); + switch( message ) + { + case kDataBrowserItemDoubleClicked: + { KeyMap theKeys; + Boolean shiftKeyDown = false, + optionKeyDown = false, + controlKeyDown = false; + GetKeys( theKeys ); + if( theKeys[1] & (shiftKey >> shiftKeyBit) ) shiftKeyDown = true; + if( theKeys[1] & (optionKey >> shiftKeyBit) ) optionKeyDown = true; + if( theKeys[1] & (controlKey >> shiftKeyBit) ) controlKeyDown = true; + if( optionKeyDown ) file->OpenResource( itemID, kMenuCommandOpenHex ); + else if( controlKeyDown ) file->OpenResource( itemID, kMenuCommandOpenTemplate ); + else file->OpenResource( itemID, kMenuCommandOpenDefault ); + } break; + + case kDataBrowserItemSelected: + case kDataBrowserItemDeselected: + case kDataBrowserSelectionSetChanged: +// file->SetHeaderText(); + if( g.inspector ) + g.inspector->Update(); + break; + + case kDataBrowserEditStarted: + case kDataBrowserEditStopped: + case kDataBrowserItemAdded: + case kDataBrowserItemRemoved: + case kDataBrowserContainerOpened: + case kDataBrowserContainerClosing: + case kDataBrowserContainerClosed: + case kDataBrowserContainerSorting: + case kDataBrowserContainerSorted: + case kDataBrowserTargetChanged: + case kDataBrowserUserStateChanged: + break; + } +} + +/*** ADD DRAG ITEM ***/ +pascal Boolean DataBrowserAddDragItem( ControlRef browser, DragRef drag, DataBrowserItemID item, DragItemRef *itemRef ) +{ + #pragma unused( item ) + + // if drag already has phfs flavour, don't add another + UInt16 numFlavours; + CountDragItemFlavors( drag, *itemRef, &numFlavours ); + if( numFlavours > 0 ) return true; + + // add 'create file' callback + if( itemRef ) *itemRef = ItemReference( item ); + FlavorFlags flags = flavorNotSaved; + DragSendDataUPP sendData = NewDragSendDataUPP( SendPromisedFile ); + SetDragSendProc( drag, sendData, browser ); + + // setup imaginary file + PromiseHFSFlavor promisedFile; + promisedFile.fileType = kResourceFileType; + promisedFile.fileCreator = kResKnifeCreator; + promisedFile.fdFlags = null; // finder flags + promisedFile.promisedFlavor = kResourceTransferType; + + // add phfs and TEXT flavours + AddDragItemFlavor( drag, *itemRef, flavorTypePromiseHFS, &promisedFile, sizeof(PromiseHFSFlavor), flags ); + return true; + +/* OSErr error = noErr; + DragReference theDragRef; + ItemReference theItemRef = 1; + + // create the drag reference + NewDrag( &theDragRef ); + if( MemError() ) return; + SetDragSendProc( theDragRef, sendProc, this ); + + RgnHandle dragRgn = NewRgn(), + subtractRgn = NewRgn(); + + // get region of dragged items, using translucent dragging where possible + Point dragOffset; + GWorldPtr imageGWorld = nil; + + resData = GetResourceData( ownerWindow ); + while( resData ) + { + if( r.selected ) + UnionRgn( r.nameIconRgn, dragRgn, dragRgn ); // add new region to rest of drag region + resData = r.next; + } + + if( g.translucentDrag ) + { + short resCounter = 0; + SetPt( &dragOffset, 0, kFileHeaderHeight ); + resData = GetResourceData( ownerWindow ); + + while( !r.selected ) + { + resCounter++; + resData = r.next; + } + + error = CreateDragImage( resData, &imageGWorld ); + if( !error ) + { + // init mask region + RgnHandle maskRgn = NewRgn(); + CopyRgn( r.nameIconRgn, maskRgn ); + OffsetRgn( maskRgn, 0, -kFileLineHeight * resCounter ); + + // init rects + Rect sourceRect, destRect; + SetRect( &sourceRect, 0, 0, g.nameColumnWidth, kFileLineHeight ); + SetRect( &destRect, 0, 0, g.nameColumnWidth, kFileLineHeight ); + OffsetRect( &destRect, 0, kFileHeaderHeight ); + + // init GWorld + PixMapHandle imagePixMap = GetGWorldPixMap( imageGWorld ); + DragImageFlags imageFlags = kDragStandardTranslucency | kDragRegionAndImage; + error = SetDragImage( theDragRef, imagePixMap, maskRgn, dragOffset, imageFlags ); + CopyBits( &GrafPtr( imageGWorld )->portBits, &GrafPtr( ownerWindow )->portBits, &sourceRect, &destRect, srcCopy, maskRgn ); + if( error ) SysBeep(0); + DisposeGWorld( imageGWorld ); + DisposeRgn( maskRgn ); + } + } + + // subtract middles from icons + MakeGlobal( ownerWindow, NewPoint(), &globalMouse ); + CopyRgn( dragRgn, subtractRgn ); // duplicate region + InsetRgn( subtractRgn, 2, 2 ); // inset it by 2 pixels + DiffRgn( dragRgn, subtractRgn, dragRgn ); // subtract subRgn from addRgn, save in nameIconRgn + OffsetRgn( dragRgn, globalMouse.h, globalMouse.v ); // change drag region to global coords + + // add flavour data to drag + error = AddDragItemFlavor( theDragRef, theItemRef, flavorTypePromiseHFS, &theFile, sizeof(PromiseHFSFlavor), theFlags ); + error = AddDragItemFlavor( theDragRef, theItemRef, kResType, nil, 0, theFlags ); + + // track the drag, then clean up + error = TrackDrag( theDragRef, theEvent, dragRgn ); + if( theDragRef ) DisposeDrag( theDragRef ); + if( subtractRgn ) DisposeRgn( subtractRgn ); + if( dragRgn ) DisposeRgn( dragRgn ); + return error == noErr; */ +} + +/*** ACCEPT DRAG ***/ +pascal Boolean DataBrowserAcceptDrag( ControlRef browser, DragRef drag, DataBrowserItemID item ) +{ + #pragma unused( browser, drag, item ) +/* OSStatus error = noErr; + Size size = null; + DragItemRef dragItem = 1; + UInt16 index, totalItems; + + CountDragItems( theDrag, &totalItems ); + for( index = 1; index <= totalItems; index++ ) + { + GetDragItemReferenceNumber( theDrag, index, &dragItem ); + error = GetFlavourDataSize( theDrag, dragItem, kDragFlavourTypeResource, &size ); +// if( error ) return false; + if( !error ) index = totalItems; // stop when valid item is reached + } + return size >= sizeof(ResTransferDesc); +*/ return true; +} + +/*** RECEIVE DRAG ***/ +pascal Boolean DataBrowserReceiveDrag( ControlRef browser, DragRef drag, DataBrowserItemID item ) +{ + #pragma unused( browser, drag, item ) + return true; +} + +/*** POSTÐPROCESS DRAG ***/ +pascal void DataBrowserPostProcessDrag( ControlRef browser, DragRef drag, OSStatus trackDragResult ) +{ + #pragma unused( browser, drag, trackDragResult ) +} + +/*** SEND PROMISED FILE ***/ +pascal OSErr SendPromisedFile( FlavorType type, void *dragSendRefCon, ItemReference item, DragReference drag ) +{ + OSErr error = noErr; + ControlRef browser = (ControlRef) dragSendRefCon; + FSSpec fileSpec; + Str255 fileName; + short vRefNum; + long dirID; + + if( type != flavorTypePromiseHFS ) return badDragFlavorErr; + + // create file + GetIndString( fileName, kFileNameStrings, kStringNewDragFileName ); + FindFolder( kOnSystemDisk, /*kTemporaryFolderType*/kDesktopFolderType, kCreateFolder, &vRefNum, &dirID ); + FSMakeFSSpec( vRefNum, dirID, fileName, &fileSpec ); + FSpCreateResFile( &fileSpec, kResKnifeCreator, kResourceFileType, smSystemScript ); + + // save resources into file + DragData clientData; // waiting for jim to add a ControlRef to DataBrowserItemUPP; bug: jim no longer works at Apple + clientData.browser = browser; + clientData.fileSpec = &fileSpec; + DataBrowserItemUPP callback = NewDataBrowserItemUPP( AddResourceToDragFile ); + /* control, container, recurse, state, callback, clientData */ + ForEachDataBrowserItem( browser, kDataBrowserNoItem, true, kDataBrowserItemIsSelected, callback, &clientData ); + + // save resources in file +/* ResourceObjectPtr resource = GetResourceData( file->window ); + short refNum = FSpOpenResFile( &fileSpec, fsRdWrPerm ); + UseResFile( refNum ); + while( resData ) + { + if( resource->Selected() ) + AddResource( resource->Data(), resource->Type(), resource->ID(), resource->Name() ); + resData = resource->Next(); + } + CloseResFile( refNum ); + UseResFile( g.appResFile ); +*/ + error = SetDragItemFlavorData( drag, item, type, &fileSpec, sizeof(FSSpec), 0 ); + return error; +} + +/*** ADD RESOURCE TO DRAG FILE ***/ +pascal void AddResourceToDragFile( DataBrowserItemID item, DataBrowserItemState state, void *clientData ) +{ + #pragma unused( state ) +// FSSpecPtr fileSpec = (FSSpecPtr) clientData; + WindowRef window = GetControlOwner( ((DragDataPtr) clientData)->browser ); + FileWindowPtr file = (FileWindowPtr) GetWindowRefCon( window ); + ResourceObjectPtr resource = file->GetResource( item ); + + // add resource to file + short oldFile = CurResFile(); + short refNum = FSpOpenResFile( ((DragDataPtr) clientData)->fileSpec, fsRdWrPerm ); + UseResFile( refNum ); + AddResource( resource->Data(), resource->Type(), resource->ID(), resource->Name() ); + if( ResError() == addResFailed ) + { + DisplayError( "\pDrag Partially Failed", "\pCould not add a resource to file." ); + } + else + { + SetResAttrs( resource->Data(), resource->Attributes() ); + ChangedResource( resource->Data() ); + + // clean up & move on + DetachResource( resource->Data() ); + } + CloseResFile( refNum ); + UseResFile( oldFile ); +} + +#else + + /*********************/ + /* FAKE DATA BROWSER */ +/*********************/ + +/*** CLEAR SELECTION ***/ +OSStatus FileWindow::ClearSelection( void ) +{ + ResourceObjectPtr resource = resourceMap; + while( resource ) + { + resource->Select( false ); + resource = resource->Next(); + } + return noErr; +} + +#endif \ No newline at end of file diff --git a/Carbon/Classes/DataBrowser.h b/Carbon/Classes/DataBrowser.h new file mode 100644 index 0000000..ea28a1c --- /dev/null +++ b/Carbon/Classes/DataBrowser.h @@ -0,0 +1,87 @@ +#include "ResKnife.h" +#include "FileWindow.h" + +#ifndef _ResKnife_DataBrowser_ +#define _ResKnife_DataBrowser_ + +/*! + * @header DataBrowser + * @discussion Handles the databrowser control on post-CarbonLib systems, and mimics it on pre-CarbonLib machines. + */ + +typedef struct +{ + ControlRef browser; + FSSpecPtr fileSpec; +} DragData, *DragDataPtr; + +/*! + * @enum DataBrowser Column IDs + * @discussion The data browser requires IDs for identifiying columns when adding data. These constants provide those IDs. + * @constant kDBNameColumn The name column, also include resource icon and disclosure triangle. + * @constant kDBTypeColumn The type column contains the four-byte resType of each resource. + * @constant kDBIDColumn The ID column contains 16-bit signed resource IDs + * @constant kDBSizeColumn The size column reports the size of each resource's data in bytes or multiples thereof. + */ +enum +{ + kDBNameColumn = FOUR_CHAR_CODE('name'), + kDBTypeColumn = FOUR_CHAR_CODE('type'), + kDBIDColumn = FOUR_CHAR_CODE('id '), + kDBSizeColumn = FOUR_CHAR_CODE('size') +}; + +const DataBrowserItemID kDataBrowserDataForkItem = 0xFFFFFFFE; // bug in data browser preventts use of 0xFFFFFFFF + +/*! + * @function AddDataBrowserColumn + * @discussion Adds columns to the data browser one at a time. + */ +void AddDataBrowserColumn( ControlRef browser, DataBrowserPropertyID column, UInt16 position ); +/*! + * @function DataBrowserItemData + * @discussion DataBrowser callback. + */ +pascal OSStatus DataBrowserItemData( ControlRef browser, DataBrowserItemID itemID, DataBrowserPropertyID property, DataBrowserItemDataRef itemData, Boolean changeValue ); +/*! + * @function SortDataBrowser + * @discussion DataBrowser callback. + */ +pascal Boolean SortDataBrowser( ControlRef browser, DataBrowserItemID itemOne, DataBrowserItemID itemTwo, DataBrowserPropertyID sortProperty ); +/*! + * @function DataBrowserMessage + * @discussion DataBrowser callback. + */ +pascal void DataBrowserMessage( ControlRef browser, DataBrowserItemID itemID, DataBrowserItemNotification message, DataBrowserItemDataRef itemData ); +/*! + * @function DataBrowserAddDragItem + * @discussion DataBrowser callback. + */ +pascal Boolean DataBrowserAddDragItem( ControlRef browser, DragRef drag, DataBrowserItemID item, DragItemRef *itemRef ); +/*! + * @function DataBrowserAcceptDrag + * @discussion DataBrowser callback. + */ +pascal Boolean DataBrowserAcceptDrag( ControlRef browser, DragRef drag, DataBrowserItemID item ); +/*! + * @function DataBrowserReceiveDrag + * @discussion DataBrowser callback. + */ +pascal Boolean DataBrowserReceiveDrag( ControlRef browser, DragRef drag, DataBrowserItemID item ); +/*! + * @function DataBrowserPostProcessDrag + * @discussion DataBrowser callback. + */ +pascal void DataBrowserPostProcessDrag( ControlRef browser, DragRef drag, OSStatus trackDragResult ); +/*! + * @function SendPromisedFile + * @discussion Creates the promised file and sends the FSSpec for it back. + */ +pascal OSErr SendPromisedFile( FlavorType theType, void *dragSendRefCon, ItemReference item, DragReference drag ); +/*! + * @function AddResourceToDragFile + * @discussion Adds each resource to the file created by SendPromisedFile. Called once per resource. + */ +pascal void AddResourceToDragFile( DataBrowserItemID item, DataBrowserItemState state, void *clientData ); + +#endif \ No newline at end of file diff --git a/Carbon/Classes/EditorWindow.cpp b/Carbon/Classes/EditorWindow.cpp new file mode 100644 index 0000000..4ef91b3 --- /dev/null +++ b/Carbon/Classes/EditorWindow.cpp @@ -0,0 +1,57 @@ +#include "EditorWindow.h" +#include "ResourceObject.h" +#include "Errors.h" +#include "Utility.h" + +extern globals g; + +/*** CREATOR ***/ +EditorWindow::EditorWindow( FileWindowPtr ownerFile, ResourceObjectPtr targetResource, WindowRef inputWindow ) : PlugWindow( ownerFile ) +{ + OSStatus error = noErr; + + // set default variables + window = inputWindow; + resource = targetResource; + + // set up default window title + Str255 windowTitle, resTypeStr, resIDStr; + FSSpec spec = *ownerFile->GetFileSpec(); + CopyPString( spec.name, windowTitle ); + TypeToPString( resource->Type(), resTypeStr ); + NumToString( resource->ID(), resIDStr ); + AppendPString( windowTitle, "\p: " ); + AppendPString( windowTitle, resTypeStr ); + AppendPString( windowTitle, "\p " ); + AppendPString( windowTitle, resIDStr ); + if( *resource->Name() != 0x00 ) // resource has name + { + AppendPString( windowTitle, "\p, Ò" ); + AppendPString( windowTitle, resource->Name() ); + AppendPString( windowTitle, "\pÓ" ); + } + + // save EditorWindow class in window's refcon + SetWindowRefCon( window, (UInt32) this ); + SetWindowKind( window, kEditorWindowKind ); + SetWindowTitle( window, windowTitle ); +} + +#if !TARGET_API_MAC_CARBON + +/*** CLOSE ***/ +OSStatus EditorWindow::Close( void ) +{ + // bug: need to tell plug it is about to die. + CloseWindow( window ); + delete this; + return noErr; +} + +#endif + +/*** RESOURCE ACCESSOR ***/ +ResourceObjectPtr EditorWindow::Resource( void ) +{ + return resource; +} \ No newline at end of file diff --git a/Carbon/Classes/EditorWindow.h b/Carbon/Classes/EditorWindow.h new file mode 100644 index 0000000..0b5c79d --- /dev/null +++ b/Carbon/Classes/EditorWindow.h @@ -0,0 +1,37 @@ +#include "ResKnife.h" +#include "PlugWindow.h" +#include "ResourceObject.h" + +#ifndef _ResKnife_EditorWindow_ +#define _ResKnife_EditorWindow_ + +/*! + * @header EditorWindow + * @discussion A class specifically designed to maintain an external editor window. + */ + +/*! + * @class EditorWindow + * @discussion A class specifically designed to maintain an external editor window. + */ +class EditorWindow : PlugWindow +{ +private: + ResourceObjectPtr resource; + Boolean modified; // flag the editor sets when it modifies a resource (ie. it needs to be saved) + +public: +/*! + * @function EditorWindow + * @discussion Constructor function. + */ + EditorWindow( FileWindowPtr ownerFile, ResourceObjectPtr targetResource, WindowRef inputWindow ); +/*! + * @function Close + * @discussion Sends a close message to the plug, then closes the window. + */ + OSStatus Close( void ); + ResourceObjectPtr Resource( void ); +}; + +#endif \ No newline at end of file diff --git a/Carbon/Classes/Errors.cpp b/Carbon/Classes/Errors.cpp new file mode 100644 index 0000000..0630d69 --- /dev/null +++ b/Carbon/Classes/Errors.cpp @@ -0,0 +1,89 @@ +#include "Errors.h" +#include "Application.h" +#include "Utility.h" + +// import globals and prefs from Application.cpp +extern globals g; +extern prefs p; + +/*** DISPLAY ANY ERROR ***/ +OSStatus DisplayError( CFStringRef errorStr ) +{ + #pragma unused( errorStr ) + return noErr; +} + +/*** DISPLAY SIMPLE ERROR ***/ +OSStatus DisplayError( ConstStr255Param errorStr ) +{ + return DisplayError( errorStr, "\p" ); +} + +/*** DISPLAY ERROR WITH EXPLANATION */ +OSStatus DisplayError( UInt16 error, UInt16 explanation ) +{ + Str255 errorStr, explanationStr; + GetIndString( errorStr, kErrorStrings, error ); + GetIndString( explanationStr, kErrorStrings, explanation ); + return DisplayError( errorStr, explanationStr ); +} + +/*** DISPLAY ERROR WITH EXPLANATION */ +OSStatus DisplayError( ConstStr255Param errorStr, ConstStr255Param explanationStr ) +{ + if( g.surpressErrors ) return noErr; + if( g.useAppearance ) + { + SInt16 item; + AlertStdAlertParamRec params = {}; + params.movable = true; + params.defaultButton = kAlertStdAlertOKButton; + params.position = kWindowDefaultPosition; + +#if __profile__ + ProfilerSetStatus( false ); +#endif + SysBeep(0); + StandardAlert( kAlertStopAlert, errorStr, explanationStr, ¶ms, &item ); +#if __profile__ + ProfilerSetStatus( true ); +#endif + return item == kAlertStdAlertOKButton? noErr:paramErr; + } + else + { + ParamText( errorStr, explanationStr, "\p", "\p" ); + ModalFilterUPP filter = null; // NewModalFilterUPP( ParseDialogEvents ); +#if __profile__ + ProfilerSetStatus( false ); +#endif + SysBeep(0); + DialogItemIndex item = StopAlert( 128, filter ); +#if __profile__ + ProfilerSetStatus( true ); +#endif + return item == kAlertStdAlertOKButton? noErr:paramErr; + } +} + +/*** DISPLAY ERROR WITH EXPLANATION */ +OSStatus DebugError( UInt16 error, OSStatus number ) +{ + Str255 errorStr; + GetIndString( errorStr, kDebugStrings, error ); + return DebugError( errorStr, number ); +} + +/*** DISPLAY A DEBUGGING ERROR ***/ +OSStatus DebugError( ConstStr255Param errorStr, OSStatus number ) +{ + OSStatus error = noErr; + if( g.debug ) + { + Str255 message = "\pDebugging Error ID: ", numString = "\p"; + NumToString( number, numString ); + AppendPString( message, numString ); + error = DisplayError( message, errorStr ); + } + return error; +} \ No newline at end of file diff --git a/Carbon/Classes/Errors.h b/Carbon/Classes/Errors.h new file mode 100644 index 0000000..fdfe4e5 --- /dev/null +++ b/Carbon/Classes/Errors.h @@ -0,0 +1,42 @@ +#include "ResKnife.h" + +#ifndef _ResKnife_Errors_ +#define _ResKnife_Errors_ + +/*! + @header Errors + @discussion Contains all error display code for both ResKnife and it's plug-ins. +*/ + +/*! + @function DisplayError + @discussion Pass a CFStringRef and ResKnife will do noting at all. (yet :) +*/ +OSStatus DisplayError( CFStringRef error ); +/*! + @function DisplayError + @discussion Pass one pascal string and ResKnife will display a simple error message. +*/ +OSStatus DisplayError( ConstStr255Param error ); +/*! + @function DisplayError + @discussion Pass two string indecies within kErrorStrings, and they will be displayed as an error message. +*/ +OSStatus DisplayError( UInt16 error, UInt16 explanation ); +/*! + @function DisplayError + @discussion Pass two pascal strings and ResKnife will display a more refined error message. +*/ +OSStatus DisplayError( ConstStr255Param error, ConstStr255Param explanation ); +/*! + @function DebugError + @discussion Pass an index of a string with kDebugStrings and if debugging mode is on, ResKnife will display the message. +*/ +OSStatus DebugError( UInt16 error, OSStatus number = noErr ); +/*! + @function DebugError + @discussion Pass a pascal string and if debugging mode is on, ResKnife will display the message. +*/ +OSStatus DebugError( ConstStr255Param errorStr, OSStatus number = noErr ); + +#endif \ No newline at end of file diff --git a/Carbon/Classes/FileWindow.cpp b/Carbon/Classes/FileWindow.cpp new file mode 100644 index 0000000..5354b00 --- /dev/null +++ b/Carbon/Classes/FileWindow.cpp @@ -0,0 +1,1799 @@ +#include "FileWindow.h" +#include "Application.h" +#include "Asynchronous.h" +#include "ResourceObject.h" +#include "Errors.h" +#include "InspectorWindow.h" +#include "PlugObject.h" // for LoadEditor() +#include "PickerWindow.h" +#include "Utility.h" +extern globals g; + +#pragma mark Constructor + +/*** FILE WINDOW CONSTRUCTOR ***/ +FileWindow::FileWindow( FSSpecPtr spec ) +{ + OSStatus error = noErr; + +#if USE_NIBS + // create a nib reference (only searches the application bundle) + IBNibRef nibRef = null; + error = CreateNibReference( CFSTR("ResKnife"), &nibRef ); + if( error != noErr || nibRef == null ) + { + DisplayError( "\pThe nib file reference could not be obtained." ); + return; + } + + // create window + error = CreateWindowFromNib( nibRef, CFSTR("File Window"), &window ); + if( error != noErr || window == null ) + { + DisplayError( "\pA file window could not be obtained from the nib file." ); + return; + } + + // dispose of nib ref + DisposeNibReference( nibRef ); + +#elif TARGET_API_MAC_CARBON + // create window + Rect creationBounds; + SetRect( &creationBounds, 0, 0, kDefaultFileWindowWidth, kDefaultFileWindowHeight ); + OffsetRect( &creationBounds, 10, 48 ); + WindowAttributes attributes = kWindowStandardDocumentAttributes | kWindowStandardHandlerAttribute | kWindowInWindowMenuAttribute; + if( g.systemVersion >= kMacOSX ) attributes |= kWindowLiveResizeAttribute; + error = CreateNewWindow( kDocumentWindowClass, attributes, &creationBounds, &window ); + if( error ) return; +#else + if( g.useAppearance && g.systemVersion >= kMacOSEight ) + { + window = GetNewCWindow( kFileWindow8, null, kFirstWindowOfClass ); + themeSavvy = true; + } + else + { + window = GetNewCWindow( kFileWindow7, null, kFirstWindowOfClass ); + themeSavvy = false; + } +#endif + + // save FileWindow class in window's refcon + SetWindowRefCon( window, (UInt32) this ); + SetWindowKind( window, kFileWindowKind ); + SetWindowTitle( window, spec->name ); +#if TARGET_API_MAC_CARBON + // set title used in window menu + SetWindowAlternateTitle( window, null ); // bug: should use path here +#endif + + // set window's background to default for theme + if( g.useAppearance ) + SetThemeWindowBackground( window, kThemeBrushModelessDialogBackgroundActive, false ); + +#if USE_NIBS // Carbon nib file on Public Beta doesn't let me set this + WindowAttributes setThese = null, clearThese = null; + setThese = kWindowInWindowMenuAttribute; + ChangeWindowAttributes( window, setThese, clearThese ); +#endif + + // initalise file variables + fileSpec = (FSSpecPtr) NewPtrClear( sizeof(FSSpec) ); + tempSpec = (FSSpecPtr) NewPtrClear( sizeof(FSSpec) ); + fileDirty = false; + SetFileSpec( spec ); +#if TARGET_API_MAC_CARBON + dataBrowser = null; +#endif + + // initalise resource variables + numTypes = 0; + numResources = 0; + dataFork = null; + resourceMap = null; + +#if TARGET_API_MAC_CARBON + // install window event handler + EventHandlerRef ref = null; + EventHandlerUPP handler = NewEventHandlerUPP( FileWindowEventHandler ); + EventTypeSpec events[] = { { kEventClassWindow, kEventWindowClose }, + { kEventClassWindow, kEventWindowBoundsChanging }, + { kEventClassWindow, kEventWindowBoundsChanged }, + { kEventClassWindow, kEventWindowGetIdealSize }, + { kEventClassWindow, kEventWindowZoomed } }; + InstallWindowEventHandler( window, handler, GetEventTypeCount(events), (EventTypeSpec *) &events, this, &ref ); + + EventTypeSpec update = { kEventClassMenu, kEventMenuEnableItems }; + EventTypeSpec process = { kEventClassCommand, kEventCommandProcess }; + + // install menu update handler + handler = NewEventHandlerUPP( FileWindowUpdateMenus ); + InstallWindowEventHandler( window, handler, 1, &update, this, &ref ); + + // install menu selection handler + handler = NewEventHandlerUPP( FileWindowParseMenuSelection ); + InstallWindowEventHandler( window, handler, 1, &process, this, &ref ); +#endif + +#if USE_NIBS + // set window property + ControlRef dataBrowser; + ControlID id = { kDataBrowserSignature, 0 }; + GetControlByID( window, &id, &dataBrowser ); + SetWindowProperty( window, kResKnifeCreator, kWindowPropertyDataBrowser, sizeof(ControlRef), &dataBrowser );; + +#elif TARGET_API_MAC_CARBON // CarbonLib 1.1+ only + // create root control + ControlRef root; + CreateRootControl( window, &root ); + + // create header control + Rect windowBounds, rect; + GetWindowPortBounds( window, &windowBounds ); + SetRect( &rect, windowBounds.left, windowBounds.top, windowBounds.right, windowBounds.bottom ); + InsetRect( &rect, -1, -1 ); + rect.bottom = rect.top + kDefaultHeaderHeight +1; + CreateWindowHeaderControl( window, &rect, true, &header ); + ControlID id = { kHeaderSignature, 0 }; + SetControlID( header, &id ); + + // create text controls + ControlFontStyleRec fontStyle; + fontStyle.flags = kControlUseFontMask + kControlUseJustMask; + fontStyle.font = kControlFontSmallSystemFont; + fontStyle.just = teJustLeft; + SetRect( &rect, windowBounds.left +4, windowBounds.top +4, (windowBounds.right - windowBounds.left) /5 *3, windowBounds.top + kDefaultHeaderHeight -4 ); + CreateStaticTextControl( window, &rect, CFSTR("left"), &fontStyle, &left ); + id.id = 0; + id.signature = kLeftTextSignature; + SetControlID( left, &id ); + + fontStyle.just = teJustRight; + SetRect( &rect, (windowBounds.right - windowBounds.left) /5 *2, windowBounds.top +4, windowBounds.right -4, windowBounds.top + kDefaultHeaderHeight -4 ); + CreateStaticTextControl( window, &rect, CFSTR("right"), &fontStyle, &right ); + id.id = 0; + id.signature = kRightTextSignature; + SetControlID( right, &id ); + + // embed text controls within header + EmbedControl( left, header ); + EmbedControl( right, header ); + + // create data browser + SetRect( &rect, windowBounds.left, windowBounds.top + kDefaultHeaderHeight +1, windowBounds.right, windowBounds.bottom ); + CreateDataBrowserControl( window, &rect, kDataBrowserListView, &dataBrowser ); + id.id = 0; + id.signature = kDataBrowserSignature; + SetControlID( dataBrowser, &id ); + SetControlReference( dataBrowser, (UInt32) this ); + SetWindowProperty( window, kResKnifeCreator, kWindowPropertyDataBrowser, sizeof(ControlRef), &dataBrowser ); + SetKeyboardFocus( window, dataBrowser, kControlDataBrowserPart ); + +#elif !TARGET_API_MAC_CARBON + // create controls + if( themeSavvy ) + { + // create basic window controls + header = GetNewControl( kFileHeaderControl, window ); + horizScroll = GetNewControl( kAppearanceScrollBarControl, window ); + vertScroll = GetNewControl( kAppearanceScrollBarControl, window ); + + // create sort buttons + Rect bounds = {}; + sortName = NewControl( window, &bounds, "\pName", true, 0, kControlBehaviorSticky + kControlContentTextOnly, 0, kControlBevelButtonSmallBevelProc, 0 ); + sortType = NewControl( window, &bounds, "\pType", true, 0, kControlBehaviorSticky + kControlContentTextOnly, 0, kControlBevelButtonSmallBevelProc, 0 ); + sortID = NewControl( window, &bounds, "\pID", true, 0, kControlBehaviorSticky + kControlContentTextOnly, 0, kControlBevelButtonSmallBevelProc, 0 ); + sortSize = NewControl( window, &bounds, "\pSize", true, 0, kControlBehaviorSticky + kControlContentTextOnly, 0, kControlBevelButtonSmallBevelProc, 0 ); + sortAttrs = NewControl( window, &bounds, "\pAttributes", true, 0, kControlBehaviorSticky + kControlContentTextOnly, 0, kControlBevelButtonSmallBevelProc, 0 ); + sortDir = NewControl( window, &bounds, "\p", true, 0, kControlContentIconSuiteRes, kSortUpIcon, kControlBevelButtonSmallBevelProc, 0 ); + nameColumnWidth = kFileWindowDefaultNameColumnWidth; + + // put the text in the right place + SetBevelButtonTextAlignment( sortName, kControlBevelButtonAlignTextFlushLeft, kFileWindowNameColumnTextOffset -1 ); + SetBevelButtonTextAlignment( sortType, kControlBevelButtonAlignTextFlushRight, 5 ); + SetBevelButtonTextAlignment( sortSize, kControlBevelButtonAlignTextFlushRight, 5 ); + SetBevelButtonTextAlignment( sortID, kControlBevelButtonAlignTextFlushRight, 5 ); + SetBevelButtonTextAlignment( sortAttrs, kControlBevelButtonAlignTextCenter, 0 ); + + // set up font details + ControlFontStylePtr fontStyle = (ControlFontStylePtr) NewPtrClear( sizeof(ControlFontStyleRec) ); + fontStyle->flags = kControlUseFontMask + kControlUseSizeMask; + fontStyle->font = kControlFontSmallSystemFont; + + // apply font details to controls + SetControlFontStyle( sortName, fontStyle ); + SetControlFontStyle( sortType, fontStyle ); + SetControlFontStyle( sortSize, fontStyle ); + SetControlFontStyle( sortID, fontStyle ); + SetControlFontStyle( sortAttrs, fontStyle ); + + // depress type control + SetControlValue( sortType, 1 ); + sortOrder = kSortType; + + // size the controls correctly + MoveControl( sortName, -1, kDefaultHeaderHeight +1 ); + SizeControl( sortName, nameColumnWidth, kBevelButtonHeight ); + + MoveControl( sortType, nameColumnWidth -1, kDefaultHeaderHeight +1 ); + SizeControl( sortType, kFileWindowTypeColumnWidth, kBevelButtonHeight ); + + MoveControl( sortID, nameColumnWidth + kFileWindowTypeColumnWidth -1, kDefaultHeaderHeight +1 ); + SizeControl( sortID, kFileWindowIDColumnWidth, kBevelButtonHeight ); + + MoveControl( sortSize, nameColumnWidth + kFileWindowTypeColumnWidth + kFileWindowIDColumnWidth -1, kDefaultHeaderHeight +1 ); + SizeControl( sortSize, kFileWindowSizeColumnWidth, kBevelButtonHeight ); + + MoveControl( sortAttrs, nameColumnWidth + kFileWindowTypeColumnWidth + kFileWindowIDColumnWidth + kFileWindowSizeColumnWidth -1, kDefaultHeaderHeight +1 ); + SizeControl( sortAttrs, kFileWindowAttributesColumnWidth, kBevelButtonHeight ); + + MoveControl( sortDir, nameColumnWidth + kFileWindowTypeColumnWidth + kFileWindowIDColumnWidth + kFileWindowSizeColumnWidth + kFileWindowAttributesColumnWidth -1, kDefaultHeaderHeight +1 ); + SizeControl( sortDir, kFileWindowSortColumnWidth, kBevelButtonHeight ); + } + else + { + nameColumnWidth = kFileWindowDefaultNameColumnWidth; + horizScroll = GetNewControl( kSystem7ScrollBarControl, window ); + vertScroll = GetNewControl( kSystem7ScrollBarControl, window ); + } + + // move & update scroll bars to where they should be :) + BoundsChanged( null ); +#endif + + // read forks into memory and + error = ReadResourceFork(); + error = ReadDataFork( error ); + if( error ) + { + delete this; + return; + } + +#if TARGET_API_MAC_CARBON // CarbonLib 1.1+ or OS X only + // initalise data browser + InitDataBrowser(); +#endif + + // now finally we can show the window + ShowWindow( window ); + new InspectorWindow; +} + +/*** DESTRUCTOR ***/ +FileWindow::~FileWindow( void ) +{ + DisposeWindow( window ); + if( fileSpec ) DisposePtr( (Ptr) fileSpec ); + if( resourceMap ) DisposeResourceMap(); +} + +/*** WINDOW ACCESSOR ***/ +WindowRef FileWindow::Window( void ) +{ + return window; +} + + /********************/ + /* EVENT PROCESSING */ +/********************/ + +#if TARGET_API_MAC_CARBON + +/*** FILE WINDOW EVENT HANDLER ***/ +pascal OSStatus FileWindowEventHandler( EventHandlerCallRef callRef, EventRef event, void *userData ) +{ + #pragma unused( callRef ) + OSStatus error = eventNotHandledErr; + unsigned long eventClass = GetEventClass( event ); + unsigned long eventKind = GetEventKind( event ); + FileWindowPtr file = (FileWindowPtr) userData; + if( !file ) return eventNotHandledErr; + + switch( eventClass ) + { + case kEventClassWindow: + switch( eventKind ) + { + case kEventWindowClose: + { WindowRef window = FrontNonFloatingWindow(), nextWindow; + while( window ) + { + nextWindow = GetNextWindow( window ); + SInt32 kind = GetWindowKind( window ); + if( kind == kPickerWindowKind || kind == kEditorWindowKind ) + { + FileWindowPtr owner = (FileWindowPtr) ((PlugWindowPtr) GetWindowRefCon( window ))->File(); + if( owner == file ) + { +// DisposeWindow( window ); // bug: windows didn't used to close when sent a WindowClose event! (seems to work now though) + EventRef event; + CreateEvent( null, kEventClassWindow, kEventWindowClose, kEventDurationNoWait, kEventAttributeNone, &event ); + SendEventToWindow( event, window ); + ReleaseEvent( event ); + } + } + window = nextWindow; + } + + // feature to add: user chooses either resource-level or file-level (or both) save sheets + if( file->IsFileDirty() ) + if( g.useSheets ) error = file->DisplayModelessAskSaveChangesDialog(); + else error = file->DisplaySaveDialog(); + else error = noErr; + if( error != userCanceledErr ) + delete file; + } break; + + case kEventWindowBoundsChanging: + error = file->BoundsChanging( event ); + break; + + case kEventWindowBoundsChanged: + error = file->BoundsChanged( event ); + break; + + case kEventWindowZoomed: + error = file->Zoomed( event ); + break; + + case kEventWindowGetIdealSize: + error = file->SetIdealSize( event ); + break; + } + break; + + case kEventClassCommand: + HICommand command; + error = GetEventParameter( event, kEventParamDirectObject, typeHICommand, null, sizeof(HICommand), null, &command ); + if( error ) return eventNotHandledErr; + else error = eventNotHandledErr; + switch( eventKind ) + { + case kEventCommandProcess: +/* switch( command.commandID ) + { + } +*/ break; + } + break; + } + return error; +} + +/*** FILE WINDOW UPDATE MENUS ***/ +pascal OSStatus FileWindowUpdateMenus( EventHandlerCallRef callRef, EventRef event, void *userData ) +{ + #pragma unused( callRef, event ) + OSStatus error = eventNotHandledErr; + + // get file window + FileWindowPtr file = (FileWindowPtr) userData; + if( !file ) return eventNotHandledErr; + + // get number of resources selected + UInt32 numSelected; + GetDataBrowserItemCount( file->GetDataBrowser(), kDataBrowserNoItem, true, kDataBrowserItemIsSelected, &numSelected ); + + // determine if selected resource is of type 'snd ' + Boolean canPlaySound = false; + ResourceObjectPtr resource = null; + DataBrowserItemID first, last, n; + GetDataBrowserSelectionAnchor( file->GetDataBrowser(), &first, &last ); + if( first != kDataBrowserNoItem && last != kDataBrowserNoItem ) + { + for( n = first; n <= last; n++ ) + { + resource = file->GetResource( n ); + if( resource->Type() == soundListRsrc ) + canPlaySound = true; + } + } + + // edit menu + EnableCommand( null, kHICommandUndo, false ); + EnableCommand( null, kHICommandRedo, false ); +/* EnableCommand( null, kHICommandCut, numSelected > 0 ); + EnableCommand( null, kHICommandCopy, numSelected > 0 ); + EnableCommand( null, kHICommandPaste, numSelected > 0 ); + EnableCommand( null, kHICommandClear, numSelected > 0 ); +*/ EnableCommand( null, kHICommandCut, false ); + EnableCommand( null, kHICommandCopy, false ); + EnableCommand( null, kHICommandPaste, false ); + EnableCommand( null, kHICommandClear, false ); + EnableCommand( null, kHICommandSelectAll, true ); + EnableCommand( null, kMenuCommandFind, false ); + EnableCommand( null, kMenuCommandFindAgain, false ); + + // resource menu + EnableCommand( null, kMenuCommandNewResource, true ); + EnableCommand( null, kMenuCommandOpenHex, numSelected > 0 ); + EnableCommand( null, kMenuCommandOpenDefault, numSelected > 0 ); + EnableCommand( null, kMenuCommandOpenTemplate, numSelected > 0 ); + EnableCommand( null, kMenuCommandOpenSpecific, numSelected > 0 ); + EnableCommand( null, kMenuCommandRevertResource, numSelected > 0 ); + EnableCommand( null, kMenuCommandPlaySound, numSelected > 0 && canPlaySound ); + return eventNotHandledErr; +} + +/*** FILE WINDOW PARSE MENU SELECTION ***/ +pascal OSStatus FileWindowParseMenuSelection( EventHandlerCallRef callRef, EventRef event, void *userData ) +{ + #pragma unused( callRef ) + + // get menu command + HICommand menuCommand; + OSStatus error = GetEventParameter( event, kEventParamDirectObject, typeHICommand, null, sizeof(HICommand), null, &menuCommand ); + if( error ) return eventNotHandledErr; + + // get file window + FileWindowPtr file = (FileWindowPtr) userData; + if( !file ) return eventNotHandledErr; + + switch( menuCommand.commandID ) + { + // file menu + case kMenuCommandCloseWindow: + case kMenuCommandCloseFile: + EventRef event; + CreateEvent( null, kEventClassWindow, kEventWindowClose, kEventDurationNoWait, kEventAttributeNone, &event ); + SendEventToWindow( event, file->Window() ); + ReleaseEvent( event ); + break; + + case kMenuCommandSaveFile: + error = file->SaveFile( null ); + break; + + case kMenuCommandSaveFileAs: + if( g.useSheets ) error = file->DisplayModelessPutFileDialog(); + else error = file->DisplaySaveAsDialog(); + break; + + case kMenuCommandRevertFile: + if( g.useSheets ) error = file->DisplayModelessAskDiscardChangesDialog(); + else error = file->DisplayRevertFileDialog(); + break; + + // edit menu +/* case kHICommandOK: + case kHICommandCancel: + case kHICommandQuit: + case kHICommandUndo: + case kHICommandRedo: + case kHICommandCut: + case kHICommandCopy: + case kHICommandPaste: + case kHICommandClear: +*/ case kHICommandSelectAll: + ForEachDataBrowserItem( file->GetDataBrowser(), kDataBrowserNoItem, true, kDataBrowserItemIsSelected, null, null ); + break; +/* case kHICommandHide: + case kHICommandPreferences: + case kHICommandZoomWindow: + case kHICommandMinimizeWindow: + case kHICommandArrangeInFront: + case kHICommandAbout: + break; +*/ + // resource menu + case kMenuCommandNewResource: + if( g.useSheets ) error = file->DisplayNewResourceSheet(); + else error = file->DisplayNewResourceDialog(); + break; + + case kMenuCommandOpenDefault: + case kMenuCommandOpenTemplate: + case kMenuCommandOpenSpecific: + case kMenuCommandOpenHex: +#if TARGET_API_MAC_CARBON + for( DataBrowserItemID item = 1; item <= file->GetResourceCount(); item++ ) + { + if( IsDataBrowserItemSelected( file->GetDataBrowser(), item ) ) + error = file->OpenResource( item, menuCommand.commandID ); + if( error ) return eventNotHandledErr; + } +#else + DisplayDialog( "\pAs I haven't wired up Classic Events, no Editors work in classic mode." ); +#endif + break; + + case kMenuCommandRevertResource: + break; + + case kMenuCommandPlaySound: + DataBrowserItemID first, last, n; + GetDataBrowserSelectionAnchor( file->GetDataBrowser(), &first, &last ); + for( n = first; n <= last; n++ ) + { + ResourceObjectPtr resource = file->GetResource( n ); + if( resource->Type() == soundListRsrc ) + file->PlaySound( n ); + } + break; + + default: + return eventNotHandledErr; + } + return error; +} + +#endif + +/*** WINDOW BOUNDS ARE CHANGING ***/ +OSStatus FileWindow::BoundsChanging( EventRef event ) +{ + OSStatus error = noErr; +#if TARGET_API_MAC_CARBON + // check that window is not just being dragged + UInt32 attributes; + error = GetEventParameter( event, kEventParamAttributes, typeUInt32, null, sizeof(UInt32), null, &attributes ); + if( error || attributes & kWindowBoundsChangeUserDrag ) return eventNotHandledErr; + if( g.systemVersion < kMacOSX ) return noErr; + + // resize window's contents + error = eventNotHandledErr; + if( g.systemVersion >= kMacOSX ) + { + Rect windowBounds; + error = GetEventParameter( event, kEventParamCurrentBounds, typeQDRectangle, null, sizeof(Rect), null, &windowBounds ); + if( error ) return eventNotHandledErr; + SizeControl( dataBrowser, windowBounds.right - windowBounds.left, windowBounds.bottom - windowBounds.top - kDefaultHeaderHeight -1 ); + } +#else + #pragma unused( event ) +#endif + return error; +} + +/*** WINDOW BOUNDS HAVE CHANGED ***/ +OSStatus FileWindow::BoundsChanged( EventRef event ) +{ +#if TARGET_API_MAC_CARBON + if( event ) + { + // check that window is not just being dragged + UInt32 attributes; + OSStatus error = GetEventParameter( event, kEventParamAttributes, typeUInt32, null, sizeof(UInt32), null, &attributes ); + if( error || attributes & kWindowBoundsChangeUserDrag ) return eventNotHandledErr; + } +#else + #pragma unused( event ) +#endif + + GrafPtr oldPort; + GetPort( &oldPort ); + SetPortWindowPort( window ); + + // move everything and invalidate the area + Rect windowBounds; + if( g.windowMgrAvailable ) GetWindowBounds( window, kWindowContentRgn, &windowBounds ); + else GetPortBounds( (CGrafPtr) window, &windowBounds ); + OffsetRect( &windowBounds, -windowBounds.left, -windowBounds.top ); + +#if TARGET_API_MAC_CARBON + // resize header & data browser + SizeControl( header, (windowBounds.right - windowBounds.left) +2, kDefaultHeaderHeight +1 ); + SizeControl( left, (windowBounds.right - windowBounds.left) /2 -4, kDefaultHeaderHeight -4 ); + SizeControl( right, (windowBounds.right - windowBounds.left) /2 -4, kDefaultHeaderHeight -4 ); + MoveControl( right, (windowBounds.right - windowBounds.left) /2, 2 ); + SizeControl( dataBrowser, windowBounds.right - windowBounds.left, windowBounds.bottom - windowBounds.top - kDefaultHeaderHeight -1 ); +#else + if( themeSavvy ) + { + nameColumnWidth = windowBounds.right - kFileWindowAllOtherColumnWidths -6; + if( nameColumnWidth < kFileWindowMinimumNameColumnWidth ) nameColumnWidth = kFileWindowMinimumNameColumnWidth; + SizeControl( header, windowBounds.right +2 - windowBounds.left, kDefaultHeaderHeight +2 ); + SizeControl( sortName, nameColumnWidth, kBevelButtonHeight ); + MoveControl( sortType, (nameColumnWidth -1), kDefaultHeaderHeight +1 ); + MoveControl( sortID, (nameColumnWidth -1) + kFileWindowTypeColumnWidth, kDefaultHeaderHeight +1 ); + MoveControl( sortSize, (nameColumnWidth -1) + kFileWindowTypeColumnWidth + kFileWindowIDColumnWidth, kDefaultHeaderHeight +1 ); + MoveControl( sortAttrs, (nameColumnWidth -1) + kFileWindowTypeColumnWidth + kFileWindowIDColumnWidth + kFileWindowSizeColumnWidth, kDefaultHeaderHeight +1 ); + MoveControl( sortDir, (nameColumnWidth -1) + kFileWindowTypeColumnWidth + kFileWindowIDColumnWidth + kFileWindowSizeColumnWidth + kFileWindowAttributesColumnWidth, kDefaultHeaderHeight +1 ); + } + MoveControl( horizScroll, -1, windowBounds.bottom - (kScrollBarWidth -1) ); + SizeControl( horizScroll, windowBounds.right - (kScrollBarWidth -3), kScrollBarWidth ); + MoveControl( vertScroll, windowBounds.right - (kScrollBarWidth -1), kFileWindowHeaderHeight ); + SizeControl( vertScroll, kScrollBarWidth, windowBounds.bottom - kFileWindowHeaderHeight - (kScrollBarWidth -2) ); + if( themeSavvy && g.systemVersion >= kMacOSEightPointFive ) + { + SetControlViewSize( horizScroll, nameColumnWidth + kFileWindowAllOtherColumnWidths ); + SetControlViewSize( vertScroll, windowBounds.bottom - (kScrollBarWidth -1) - kFileWindowHeaderHeight ); + } + UpdateScrollBars(); +#endif + +#if TARGET_API_MAC_CARBON + InvalidateWindowRect( window, &windowBounds ); +#else + if( g.windowMgrAvailable ) InvalidateWindowRect( window, &windowBounds ); + else InvalidateRect( &windowBounds ); +#endif + SetPort( oldPort ); + return noErr; +} + +#if TARGET_API_MAC_CARBON + +/*** WINDOW HAS BEEN ZOOMED ***/ +OSStatus FileWindow::Zoomed( EventRef event ) +{ + // get new bounds + WindowRef window; + Rect windowBounds; + OSStatus error = GetEventParameter( event, kEventParamDirectObject, typeWindowRef, null, sizeof(WindowRef), null, &window ); + if( error ) return eventNotHandledErr; + GetWindowBounds( window, kWindowGlobalPortRgn, &windowBounds ); + OffsetRect( &windowBounds, -windowBounds.left, -windowBounds.top ); + + // resize controls to the window size + SizeControl( header, windowBounds.right - windowBounds.left +2, kDefaultHeaderHeight +1 ); + SizeControl( left, (windowBounds.right - windowBounds.left) /5 *3 - windowBounds.left +4, kDefaultHeaderHeight - windowBounds.top -8 ); + MoveControl( right, (windowBounds.right - windowBounds.left) /5 *2, windowBounds.top +4 ); + SizeControl( right, windowBounds.right - ((windowBounds.right - windowBounds.left) /5 *2) -4, kDefaultHeaderHeight - windowBounds.top -8 ); + SizeControl( dataBrowser, windowBounds.right - windowBounds.left, windowBounds.bottom - windowBounds.top - kDefaultHeaderHeight -1 ); + return error; +} + +/*** SET IDEAL SIZE OF WINDOW ***/ +OSStatus FileWindow::SetIdealSize( EventRef event ) +{ + Point idealSize; + SetPoint( &idealSize, 512, 512 ); + OSStatus error = SetEventParameter( event, kEventParamDimensions, typeQDPoint, sizeof(Point), &idealSize ); + return error? eventNotHandledErr:noErr; +} + +#endif +#if !TARGET_API_MAC_CARBON + +/*** UPDATE WINDOW ***/ +OSStatus FileWindow::Update( RgnHandle update ) +{ + GrafPtr oldPort; + GetPort( &oldPort ); + SetPortWindowPort( window ); + + // get rect into which I can draw + Rect windowBounds, rect; + if( g.windowMgrAvailable ) GetWindowBounds( window, kWindowContentRgn, &windowBounds ); + else GetPortBounds( (CGrafPort *) window, &windowBounds ); + SetRect( &rect, 0, 0, windowBounds.right - windowBounds.left, windowBounds.bottom - windowBounds.top ); + rect.top += kFileWindowHeaderHeight +1; + rect.bottom -= (kScrollBarWidth -1); + rect.right -= (kScrollBarWidth -1); + + // only draw resources if they are in the update region + if( !update || RectInRgn( &windowBounds, update ) ) + { + RgnHandle clip = NewRgn(), oldClip = NewRgn(); + RectToRegion( clip, &rect ); + GetPortClipRegion( GetWindowPort( window ), oldClip ); + SetPortClipRegion( GetWindowPort( window ), clip ); + + SInt16 value = GetControlValue( vertScroll ); + if( themeSavvy ) + { + // create light bg for all columns + RGBBackColor( &g.bgColour ); + EraseRect( &rect ); + + // add dark bg for sorted column + Rect controlBounds; + switch( sortOrder ) + { + case kSortName: + GetControlBounds( sortName, &controlBounds ); + rect.left = controlBounds.left; + rect.right = controlBounds.right; + break; + + case kSortType: + GetControlBounds( sortType, &controlBounds ); + rect.left = controlBounds.left; + rect.right = controlBounds.right; + break; + + case kSortID: + GetControlBounds( sortID, &controlBounds ); + rect.left = controlBounds.left; + rect.right = controlBounds.right; + break; + + case kSortSize: + GetControlBounds( sortSize, &controlBounds ); + rect.left = controlBounds.left; + rect.right = controlBounds.right; + break; + + case kSortAttrs: + GetControlBounds( sortAttrs, &controlBounds ); + rect.left = controlBounds.left; + rect.right = controlBounds.right; + break; + + default: + GetControlBounds( sortAttrs, &controlBounds ); + rect.left = controlBounds.right; + rect.right = controlBounds.right +1; + } + + RGBBackColor( &g.sortColour ); + EraseRect( &rect ); + } + else + { + // erase background + RGBBackColor( &g.white ); + EraseRect( &rect ); + } + + // draw all resources + ResourceObjectPtr current = resourceMap; + for( UInt16 line = 1; line <= numResources; line++ ) + { + // draw resource icon + DrawResourceIcon( current, line ); + + // reset text incase it was condensed or emboldened + RGBForeColor( &g.black ); + TextFace( normal ); + + // resource type + Str255 typeStr = "\p"; + TypeToPString( current->Type(), typeStr ); + MoveTo( nameColumnWidth + kFileWindowTypeColumnOffset + kFileWindowTypeColumnWidth - StringWidth(typeStr) -5, kFileWindowHeaderHeight + (kFileWindowRowHeight * line) - (kFileWindowRowHeight - kFileWindowTextHeight)/2 - 3 - value ); + DrawString( typeStr ); + + // resource ID + Str255 idStr; + NumToString( current->ID(), idStr ); + MoveTo( nameColumnWidth + kFileWindowIDColumnOffset + kFileWindowIDColumnWidth - StringWidth(idStr) -5, kFileWindowHeaderHeight + (kFileWindowRowHeight * line) - (kFileWindowRowHeight - kFileWindowTextHeight)/2 - 3 - value ); + DrawString( idStr ); + + // resource size + Str255 sizeStr; + NumToString( current->Size(), sizeStr ); + MoveTo( nameColumnWidth + kFileWindowSizeColumnOffset + kFileWindowSizeColumnWidth - StringWidth(sizeStr) -5, kFileWindowHeaderHeight + (kFileWindowRowHeight * line) - (kFileWindowRowHeight - kFileWindowTextHeight)/2 - 3 - value ); + DrawString( sizeStr ); + + // resource attribute icons +/* Rect iconRect; + SetRect( &iconRect, 0, 0, 16, 16 ); + OffsetRect( &iconRect, kFileWindowSizeColumnWidth + kAttrColumnOffset, (kFileLineHeight * line) +1 ); + if( current->Attributes() & resPreload) PlotIconID( &iconRect, kAlignNone, kTransformNone, kPreloadIcon ); + else PlotIconID( &iconRect, kAlignNone, kTransformDisabled, kPreloadIcon ); + OffsetRect( &iconRect, kAttrIconSep, 0 ); + if( current->Attributes() & resProtected) PlotIconID( &iconRect, kAlignNone, kTransformNone, kProtectedIcon ); + else PlotIconID( &iconRect, kAlignNone, kTransformDisabled, kProtectedIcon ); + OffsetRect( &iconRect, kAttrIconSep, 0 ); + if( current->Attributes() & resLocked) PlotIconID( &iconRect, kAlignNone, kTransformNone, kLockedIcon ); + else PlotIconID( &iconRect, kAlignNone, kTransformDisabled, kLockedIcon ); + OffsetRect( &iconRect, kAttrIconSep, 0 ); + if( current->Attributes() & resPurgeable) PlotIconID( &iconRect, kAlignNone, kTransformNone, kPurgableIcon ); + else PlotIconID( &iconRect, kAlignNone, kTransformDisabled, kPurgableIcon ); + OffsetRect( &iconRect, kAttrIconSep, 0 ); + if( current->Attributes() & resSysHeap) PlotIconID( &iconRect, kAlignNone, kTransformNone, kSysHeapIcon ); + else PlotIconID( &iconRect, kAlignNone, kTransformDisabled, kSysHeapIcon ); + OffsetRect( &iconRect, kAttrIconSep, 0 ); + if( current->Attributes() & null) PlotIconID( &iconRect, kAlignNone, kTransformNone, kCompressedIcon ); + else PlotIconID( &iconRect, kAlignNone, kTransformDisabled, kCompressedIcon ); +*/ + /* resSysHeap = 64, // System or application heap? + resPurgeable = 32, // Purgeable resource? + resLocked = 16, // Load it in locked? + resProtected = 8, // Protected? + resPreload = 4, // Load in on OpenResFile? + resChanged = 2, // Resource changed? - not used by ResKnife */ + + if( themeSavvy ) + { + // draw divider line + MoveTo( 0, kFileWindowHeaderHeight + (kFileWindowRowHeight * line) - value ); + RGBForeColor( &g.white ); + Line( windowBounds.right - windowBounds.left -1, 0 ); + } + + // move on + current = current->Next(); + } + + SetPortClipRegion( GetWindowPort( window ), oldClip ); + DisposeRgn( clip ); + DisposeRgn( oldClip ); + } + + // update the controls + if( !themeSavvy ) DrawGrowIcon( window ); + UpdateControls( window, update ); + SetPort( oldPort ); + return noErr; +} + +/*** ACTIVATE WINDOW ***/ +OSStatus FileWindow::Activate( Boolean active ) +{ + // invalidate the whole window + GrafPtr oldPort; + GetPort( &oldPort ); + SetPortWindowPort( window ); + + Rect windowBounds; + if( g.windowMgrAvailable ) GetWindowBounds( window, kWindowContentRgn, &windowBounds ); + else GetPortBounds( (CGrafPort *) window, &windowBounds ); + if( g.windowMgrAvailable ) InvalidateWindowRect( window, &windowBounds ); + else InvalidateRect( &windowBounds ); + SetPort( oldPort ); + + // set the controls to display correctly + ControlPartCode hiliteState = active? kControlNoPart:kControlInactivePart; + if( themeSavvy ) + { + HiliteControl( header, hiliteState ); + HiliteControl( sortName, hiliteState ); + HiliteControl( sortType, hiliteState ); + HiliteControl( sortID, hiliteState ); + HiliteControl( sortSize, hiliteState ); + HiliteControl( sortAttrs, hiliteState ); + HiliteControl( sortDir, hiliteState ); + } + HiliteControl( horizScroll, hiliteState ); + HiliteControl( vertScroll, hiliteState ); + return noErr; +} + +/*** MOUSE CLICK ***/ +OSStatus FileWindow::Click( Point globalMouse, EventModifiers modifiers ) +{ + // set the mouse location to local co-ords + GrafPtr oldPort; + GetPort( &oldPort ); + SetPortWindowPort( window ); + Point localMouse = globalMouse, windowLoc = {0,0}; + GlobalToLocal( &localMouse ); + LocalToGlobal( &windowLoc ); + SetPort( oldPort ); + + // handle a click on a control first + ControlHandle control; + SInt16 controlPart; + if( themeSavvy ) control = FindControlUnderMouse( localMouse, window, &controlPart ); + else controlPart = FindControl( localMouse, window, &control ); + if( control && controlPart != kControlNoPart ) + { + // scroll the windoow + if( themeSavvy ) + { + ControlActionUPP scrollAction = NewControlActionUPP( FileWindowScrollAction ); + controlPart = HandleControlClick( control, localMouse, modifiers, scrollAction ); + DisposeControlActionUPP( scrollAction ); + } + else if( controlPart != kControlIndicatorPart ) + { + ControlActionUPP scrollAction = NewControlActionUPP( FileWindowScrollAction ); + TrackControl( control, localMouse, scrollAction ); + DisposeControlActionUPP( scrollAction ); + } + else // clicks in the thumb cause crashes if given a ControlActionUPP + { + controlPart = TrackControl( control, localMouse, nil ); + Update(); + } + + // save new offset & return + UpdateScrollBars(); + return noErr; + } + else + { + // click was not on a controlÉ + ResourceObjectPtr resource = resourceMap; + static unsigned long clickTime; + + // check shift key status + Boolean shiftKeyDown = false, + optionKeyDown = false; + + KeyMap theKeys; + GetKeys( theKeys ); + if( theKeys[1] & (shiftKey >> shiftKeyBit) ) shiftKeyDown = true; + if( theKeys[1] & (optionKey >> shiftKeyBit) ) optionKeyDown = true; + + // clicked on a part of the window where no resource is listed + SInt16 newSelection = ( localMouse.v + GetControlValue(vertScroll) - kFileWindowHeaderHeight ) / kFileWindowRowHeight +1; + if( newSelection > numResources ) + { + ClearSelection(); // should go to drag selection instead + Update(); + return noErr; + } + + // cycle to clicked resource + for( newSelection; newSelection > 1; newSelection-- ) + resource = resource->Next(); + + // check for click on attributes +/* if( mouse.h >= g.nameColumnWidth + kAttrColumnOffset ) // clicked on attribute + { + short attrClicked = mouse.h; + attrClicked -= g.nameColumnWidth + kAttrColumnOffset; + attrClicked /= kAttrIconSep; + attrClicked += resPreloadBit; + if( (r.attrs >> attrClicked) & 0x01 ) r.attrs -= 1 << attrClicked; + else r.attrs += 1 << attrClicked; + + // mark file & resource for saving + dirty = true; + r.dirty = true; + ChangedResource( r.resource ); + + // update the window + Update(); + } + + // if in nameIconRgn, must be a click on a resource + else*/ if( PtInRgn( localMouse, resource->nameIconRgn ) ) + { + if( resource->Selected() == false ) + { + if( shiftKeyDown ) // shift click on unselected + { + resource->Select( true ); + numSelected += 1; + } + else // click on unselected + { + clickTime = TickCount(); + ClearSelection(); + resource->Select( true ); + numSelected = 1; + } + } + else + { + if( TickCount() <= clickTime + GetDblTime() ) // double click + { + ClearSelection(); + resource->Select( true ); + numSelected = 1; + OpenResource( resource->Number(), optionKeyDown? kMenuCommandOpenHex:kMenuCommandOpenDefault ); + } + else if( shiftKeyDown ) // shift click on selected + { + resource->Select( false ); + numSelected -= 1; + } + else // click on selected + { + clickTime = TickCount(); + resource->Select( true ); + } + } + + // update the window + Update(); + + // check for resource drags + if( WaitMouseMoved( globalMouse ) ) // drag activated + { + OSErr error = noErr; + DragReference theDragRef; + ItemReference theItemRef = 1; + FlavorFlags theFlags = flavorNotSaved; + DragSendDataUPP sendProc = null; // bug: NewDragSendDataProc( SendFileDataProc ); + + // setup imaginary file + PromiseHFSFlavor theFile; + theFile.fileType = kResourceTransferType; + theFile.fileCreator = kResKnifeCreator; + theFile.fdFlags = null; // finder flags + theFile.promisedFlavor = kResourceTransferType; + + // create the drag reference + NewDrag( &theDragRef ); + error = MemError(); + if( error ) return error; + SetDragSendProc( theDragRef, sendProc, this ); + + RgnHandle dragRgn = NewRgn(), + subtractRgn = NewRgn(); + + // get region of dragged items, using translucent dragging where possible + GWorldPtr imageGWorld = null; + + resource = resourceMap; + while( resource ) + { + if( resource->Selected() ) + UnionRgn( resource->nameIconRgn, dragRgn, dragRgn ); // add new region to rest of drag region + resource = resource->Next(); + } + +/* if( g.translucentDrag ) + { + Point dragOffset; + DataBrowserItemID resCounter = 0; + SetPt( &dragOffset, 0, kFileWindowHeaderHeight ); + resource = resourceMap; + + while( !resource->Selected() ) + { + resCounter++; + resource = resource->Next(); + } + + error = CreateDragImage( resource, &imageGWorld ); + if( !error ) + { + // init mask region + RgnHandle maskRgn = NewRgn(); + CopyRgn( resource->nameIconRgn, maskRgn ); + OffsetRgn( maskRgn, 0, -kFileWindowRowHeight * resCounter ); + + // init rects + Rect sourceRect, destRect; + SetRect( &sourceRect, 0, 0, nameColumnWidth, kFileWindowRowHeight ); + SetRect( &destRect, 0, 0, nameColumnWidth, kFileWindowRowHeight ); + OffsetRect( &destRect, 0, kFileWindowHeaderHeight ); + + // init GWorld + PixMapHandle imagePixMap = GetGWorldPixMap( imageGWorld ); + DragImageFlags imageFlags = kDragStandardTranslucency | kDragRegionAndImage; + error = SetDragImage( theDragRef, imagePixMap, maskRgn, dragOffset, imageFlags ); + CopyBits( &GrafPtr( imageGWorld )->portBits, &GrafPtr( window )->portBits, &sourceRect, &destRect, srcCopy, maskRgn ); + if( error ) SysBeep(0); + DisposeGWorld( imageGWorld ); + DisposeRgn( maskRgn ); + } + } +*/ + // subtract middles from icons + CopyRgn( dragRgn, subtractRgn ); // duplicate region + InsetRgn( subtractRgn, 2, 2 ); // inset it by 2 pixels + DiffRgn( dragRgn, subtractRgn, dragRgn ); // subtract subRgn from addRgn, save in nameIconRgn + OffsetRgn( dragRgn, windowLoc.h, windowLoc.v ); // change drag region to global coords + + // add flavour data to drag + error = AddDragItemFlavor( theDragRef, theItemRef, flavorTypePromiseHFS, &theFile, sizeof(PromiseHFSFlavor), theFlags ); + error = AddDragItemFlavor( theDragRef, theItemRef, kResourceTransferType, null, 0, theFlags ); + + // track the drag, then clean up + EventRecord event; + event.what = mouseDown; + event.message = null; + event.when = TickCount(); + event.where = globalMouse; + event.modifiers = modifiers; + + error = TrackDrag( theDragRef, &event, dragRgn ); + if( theDragRef ) DisposeDrag( theDragRef ); + if( subtractRgn ) DisposeRgn( subtractRgn ); + if( dragRgn ) DisposeRgn( dragRgn ); + } + } + + // otherwise, must be a drag selection or de-selection + else + { + // clear current selections + if( shiftKeyDown == false ) + { + ClearSelection(); + Update(); + } + + // check for resource drags + if( WaitMouseMoved( globalMouse ) ) + { + // set pen + PenState oldState, dragState; + GetPenState( &oldState ); + PenSize( 2, 2 ); + PenPat( &qd.gray ); + PenMode( srcXor ); + GetPenState( &dragState ); + SetPenState( &oldState ); + + // do drag selection + +// RgnHandle oldClip = NewRgn(); +// GetClip( oldClip ); +// SetClip( bodyRgn ); + + Point mouse, savedMouse = globalMouse, oldMouse = globalMouse; + Rect dragRect = {0,0,0,0}; + while( WaitMouseUp() ) + { + GetMouse( &mouse ); + SetPortWindowPort( window ); + if( mouse.h != oldMouse.h || mouse.v != oldMouse.v ) + { + SetPenState( &dragState ); + FrameRect( &dragRect ); + SetRect( &dragRect, (savedMouse.h < mouse.h)? savedMouse.h : mouse.h, + (savedMouse.v < mouse.v)? savedMouse.v : mouse.v, + (savedMouse.h > mouse.h)? savedMouse.h : mouse.h, + (savedMouse.v > mouse.v)? savedMouse.v : mouse.v ); + OffsetRect( &dragRect, -windowLoc.h, -windowLoc.v ); + FrameRect( &dragRect ); + SetPenState( &oldState ); + + // check to see if selection region coincides with resources + short i; + RgnHandle dragRgn = NewRgn(), resultRgn = NewRgn(); + RectRgn( dragRgn, &dragRect ); + ResourceObjectPtr resource = resourceMap; + for( i = 1; i <= GetControlValue(vertScroll); i++ ) + resource = resource->Next(); // move to first res in window + for( i = GetControlValue(vertScroll); i < numResources - GetControlValue(vertScroll); i++ ) + { + SetEmptyRgn( resultRgn ); + SectRgn( dragRgn, resource->nameIconRgn, resultRgn ); + resource->Select( !EmptyRgn( resultRgn ) ); + DrawResourceIcon( resource, i+1 ); + resource = resource->Next(); + } + DisposeRgn( resultRgn ); + DisposeRgn( dragRgn ); + } + SetPort( oldPort ); + oldMouse = mouse; + } + + // restore clip region +// SetClip( oldClip ); + } + + // update the window + Update(); + } + return eventNotHandledErr; + } +} + +/*** FILE WINDOW SCROLL ACTION ***/ +pascal void FileWindowScrollAction( ControlHandle control, SInt16 controlPart ) +{ + // get owning window + if( !controlPart ) return; + SInt8 state = HGetState( (Handle) control ); + HLock( (Handle) control ); + WindowRef window = GetControlOwner( control ); + HSetState( (Handle) control, state ); + WindowObjectPtr winObj = (WindowObjectPtr) GetWindowRefCon( window ); + + // get window bounds + Rect windowBounds; + if( g.windowMgrAvailable ) GetWindowBounds( window, kWindowContentRgn, &windowBounds ); + else GetPortBounds( (CGrafPort *) window, &windowBounds ); + windowBounds.top += kFileWindowHeaderHeight +1; + windowBounds.bottom -= (kScrollBarWidth -1); + windowBounds.right -= (kScrollBarWidth -1); + UInt16 windowHeight = windowBounds.bottom - windowBounds.top; + + // decide how many pixels to scroll (and in which direction) + SInt16 delta = 0; + switch( controlPart ) + { + case kControlUpButtonPart: // up (20) + delta = -kFileWindowRowHeight; + break; + + case kControlDownButtonPart: // down (21) + delta = kFileWindowRowHeight; + break; + + case kControlPageUpPart: // page up (22) + delta = kFileWindowRowHeight - windowHeight; + break; + + case kControlPageDownPart: // page down (23) + delta = windowHeight - kFileWindowRowHeight; + break; + } + + // calculate and correct control value + SInt16 max = GetControlMaximum( control ); + SInt16 startValue = GetControlValue( control ); + SInt16 endValue = startValue + delta; + if( endValue < 0 ) endValue = 0; + if( endValue > max ) endValue = max; + + // only update window if anything changed + if( endValue != startValue || controlPart == kControlIndicatorPart ) + { + SetControlValue( control, endValue ); + winObj->Update(); + } +} + +#endif + +/*** NEW RESOURCE SHEET WINDOW EVENT HANDLER ***/ +pascal OSStatus NewResourceEventHandler( EventHandlerCallRef callRef, EventRef event, void *userData ) +{ + #pragma unused( callRef, userData ) + // get control that was hit + UInt32 command; + ControlRef control; + GetEventParameter( event, kEventParamDirectObject, typeControlRef, null, sizeof(ControlRef), null, &control ); + GetControlCommandID( control, &command ); + + // get parent window (so resource is added to correct file) + WindowRef parent; + WindowRef sheet = GetControlOwner( control ); + GetSheetWindowParent( sheet, &parent ); + FileWindowPtr file = (FileWindowPtr) GetWindowRefCon( parent ); + + // do the button action + switch( command ) + { + case kHICommandOK: + { // close the sheet + HideSheetWindow( sheet ); + DisposeWindow( sheet ); + + // extract all the info about the resource we need + Str255 name = "\pTest Resource Beta"; + ResType type = 'test'; + SInt16 resID = 129; + SInt16 attribs = 0; + file->CreateNewResource( name, type, resID, attribs ); + } break; + + case kHICommandCancel: + // close the sheet + HideSheetWindow( sheet ); + DisposeWindow( sheet ); + break; + + default: + DebugError( "\pCommand assigned to that control was not dealt with." ); + } + return noErr; +} + + /********************************/ + /* FILE WINDOW DRAWING ROUTINES */ +/********************************/ + +#if !TARGET_API_MAC_CARBON + +/*** UPDATE SCROLL BARS ***/ +OSStatus FileWindow::UpdateScrollBars( void ) +{ + // get rect into which I can draw + Rect windowBounds; + if( g.windowMgrAvailable ) GetWindowBounds( window, kWindowContentRgn, &windowBounds ); + else GetPortBounds( (CGrafPort *) window, &windowBounds ); + windowBounds.top += kFileWindowHeaderHeight +1; + windowBounds.bottom -= (kScrollBarWidth -1); + windowBounds.right -= (kScrollBarWidth -1); + UInt16 windowHeight = windowBounds.bottom - windowBounds.top; + + // horizontal scroll bar + if( nameColumnWidth + kFileWindowAllOtherColumnWidths - windowBounds.right <= 0 ) + SetControlMaximum( horizScroll, 0 ); + else SetControlMaximum( horizScroll, nameColumnWidth + kFileWindowAllOtherColumnWidths - windowBounds.right ); + + // vertical scroll bar + SInt16 value = GetControlValue( vertScroll ); + if( windowHeight > numResources * kFileWindowRowHeight - value ) + SetControlMaximum( vertScroll, value ); + else SetControlMaximum( vertScroll, numResources * kFileWindowRowHeight - windowHeight ); + return noErr; +} + +/*** DRAW RESOURCE ICON ***/ +OSStatus FileWindow::DrawResourceIcon( ResourceObjectPtr resource, UInt16 line ) +{ + OSStatus error = noErr; + Rect nameRect, iconRect; + Str255 nameStr; + IconAlignmentType alignment; + IconTransformType transformation; + SInt16 value = GetControlValue( vertScroll ); + + // resource icon + alignment = kAlignNone; + if( resource->Selected() ) transformation = kTransformSelected; + else transformation = kTransformNone; + SetRect( &iconRect, 0, 0, 16, 16 ); + OffsetRect( &iconRect, kFileWindowNameColumnTextOffset -20, kFileWindowHeaderHeight + (kFileWindowRowHeight * line) - (kFileWindowRowHeight/2) - 8 - value ); + + IconSuiteRef iconSuite; + GetIconSuite( &iconSuite, kDefaultResourceIcon, kSelectorAllSmallData ); + error = PlotIconSuite( &iconRect, alignment, transformation, iconSuite ); + if( error ) return error; + +/* IconSelectorValue whichIcons = kSelectorSmall32Bit | kSelectorSmall8BitMask; + IconFamilyHandle iconFamily; + IconSuiteRef iconSuite; + + iconFamily = (IconFamilyHandle) Get1Resource( kIconFamilyType, kDefaultResourceIcon ); + IconFamilyToIconSuite( iconFamily, whichIcons, &iconSuite ); + PlotIconSuite( &iconRect, alignment, transformation, iconSuite ); + DisposeIconSuite( iconSuite, false ); + ReleaseResource( (Handle) iconFamily ); +*/ + // get name + if( *resource->Name() != 0x00 ) + { + CopyPString( resource->name, nameStr ); + } + else + { + Str255 unnamedStr; + if( resource->dataFork ) GetIndString( unnamedStr, kResourceNameStrings, kStringDataFork ); + else if( resource->ID() == -16455 && (resource->Type() == 'icns' || resource->Type() == 'icl8' || resource->Type() == 'icl4' || resource->Type() == 'ICN#' || resource->Type() == 'ics8' || resource->Type() == 'ics4' || resource->Type() == 'ics#') ) + GetIndString( unnamedStr, kResourceNameStrings, kStringCustomIcon ); + else GetIndString( unnamedStr, kResourceNameStrings, kStringUntitledResource ); + CopyPString( unnamedStr, nameStr ); + } + + // condense & italicise if necessary + if( StringWidth( nameStr ) > nameColumnWidth - 45 ) + { + if( resource->Dirty() ) TextFace( condense | bold ); + else TextFace( condense ); + } + else + { + if( resource->Dirty() ) TextFace( bold ); + else TextFace( normal ); + } + + // add ellipsis if necessary + while( StringWidth( nameStr ) > nameColumnWidth - kFileWindowNameColumnTextOffset -3 ) + { + nameStr[0] -= 1; // shorten string + nameStr[ nameStr[0] ] = 0xC9; // replace new last char with 'É' + } + + // set text/box colour + if( *resource->Name() == 0x00 ) + RGBForeColor( &g.frameColour ); + else RGBForeColor( &g.black ); + + // draw hilight box if selected + if( resource->Selected() ) + { + SetRect( &nameRect, 0, 0, StringWidth( nameStr ) +4, kFileWindowTextHeight ); + OffsetRect( &nameRect, kFileWindowNameColumnTextOffset -2, kFileWindowHeaderHeight + (kFileWindowRowHeight * line) - (kFileWindowRowHeight - kFileWindowTextHeight)/2 - kFileWindowTextHeight - value ); + PaintRect( &nameRect ); + RGBForeColor( &g.white ); // set text colour to white + } + + // draw name in set colour + SInt16 font; + GetFNum( "\pGeneva", &font ); + TextFont( font ); + TextSize( 10 ); +// TextFont( kControlFontSmallSystemFont ); // doesn't give right effect + MoveTo( kFileWindowNameColumnTextOffset, kFileWindowHeaderHeight + (kFileWindowRowHeight * line) - (kFileWindowRowHeight - kFileWindowTextHeight)/2 - 3 - value ); + DrawString( nameStr ); + RGBForeColor( &g.black ); + + // create name & icon region for click detection and dragging + RgnHandle addRgn = NewRgn(); + if( !addRgn ) + { + DisposeIconSuite( iconSuite, true ); + return memFullErr; + } + + if( resource->nameIconRgn ) + DisposeRgn( resource->nameIconRgn ); + resource->nameIconRgn = NewRgn(); + if( !resource->nameIconRgn ) + { + DisposeRgn( addRgn ); + DisposeIconSuite( iconSuite, true ); + return memFullErr; + } + + // resource icon + SetRect( &iconRect, 0, 0, 16, 16 ); + OffsetRect( &iconRect, 22, kFileWindowHeaderHeight + (kFileWindowRowHeight * (line-1)) +2 ); + IconSuiteToRgn( resource->nameIconRgn, &iconRect, kAlignNone, iconSuite ); + + // resource name + SetRect( &nameRect, 0, 0, StringWidth( nameStr ) +4, kFileWindowTextHeight ); + OffsetRect( &nameRect, 40, kFileWindowHeaderHeight + (kFileWindowRowHeight * (line-1)) +3 ); + RectRgn( addRgn, &nameRect ); // convert textRect to region + UnionRgn( addRgn, resource->nameIconRgn, resource->nameIconRgn ); // add new region to icon region + DisposeRgn( addRgn ); + DisposeIconSuite( iconSuite, true ); + return error; +} + +#endif + + /*****************************/ + /* RESOURCE MAP MANIPULATION */ +/*****************************/ + +/*** DISPLAY NEW RESOURCE SHEET ***/ +OSStatus FileWindow::DisplayNewResourceSheet( void ) +{ +#if TARGET_API_MAC_CARBON + // create a nib reference (only searches the application bundle) + IBNibRef nibRef = null; + OSStatus error = CreateNibReference( CFSTR("ResKnife"), &nibRef ); + if( error != noErr ) + { + DisplayError( "\pThe nib file reference could not be obtained." ); + return error; + } + + // create save sheet + WindowRef sheet; + error = CreateWindowFromNib( nibRef, CFSTR("New Resource"), &sheet ); + if( error != noErr ) + { + DisplayError( "\pA sheet window could not be obtained from the nib file." ); + return error; + } + + // dispose of nib ref + DisposeNibReference( nibRef ); + + // install window event handler + EventTypeSpec events = { kEventClassControl, kEventControlHit }; + EventHandlerUPP eventHandler = NewEventHandlerUPP( NewResourceEventHandler ); + InstallWindowEventHandler( sheet, eventHandler, 1, &events, 0, null ); + + // show sheet window + ShowSheetWindow( sheet, window ); + return error; +#else + return eventNotHandledErr; +#endif +} + +/*** DISPLAY NEW RESOURCE DIALOG ***/ +OSStatus FileWindow::DisplayNewResourceDialog( void ) +{ + // create and show dialog + OSStatus error = noErr; + GrafPtr oldPort; + DialogRef dialog = GetNewDialog( kNewResourceDialog, null, kFirstWindowOfClass ); + SetDialogDefaultItem( dialog, ok ); + GetPort( &oldPort ); + SetPortWindowPort( GetDialogWindow(dialog) ); + ShowWindow( GetDialogWindow(dialog) ); + + // set up variables + Str255 name = "\pTest Resource Alpha"; + ResType type = 'test'; + SInt16 resID = 128; + SInt16 attributes = 0x0000; + + // handle dialog events + Rect box; + Handle item; + short itemHit; + DialogItemType itemType; + do + { + ModalDialog( null, &itemHit ); + GetDialogItem( dialog, itemHit, &itemType, &item, &box ); + switch( itemHit ) + { + case 5: + { Str255 popupText; + MenuRef popupMenu = GetMenu( 140 ); + SInt16 popupItem = GetControlValue( (ControlRef) item ); + GetMenuItemText( popupMenu, popupItem, popupText ); + GetDialogItem( dialog, 4, &itemType, &item, &box ); + SetDialogItemText( item, popupText ); + if( popupItem == 1 ) SelectDialogItemText( dialog, 4, 0x0000, 0x0000 ); + else SelectDialogItemText( dialog, 4, 0x0000, 0xFFFF ); + RgnHandle updateRgn = NewRgn(); + RectRgn( updateRgn, &box ); + UpdateDialog( dialog, updateRgn ); + DisposeRgn( updateRgn ); + DisposeMenu( popupMenu ); + } break; + + case 7: + case 8: + case 9: + case 10: + case 11: + { attributes ^= 1 << (itemHit -5); + SetControlValue( (ControlRef) item, (short) (attributes & 1 << (itemHit -5)) ); + } break; + } + } while( itemHit != ok && itemHit != cancel ); + + HideWindow( GetDialogWindow(dialog) ); + if( itemHit == cancel ) + { + DisposeDialog( dialog ); + SetPort( oldPort ); + return userCanceledErr; + } + else + { + + // get resource name + GetDialogItem( dialog, 3, &itemType, &item, &box ); + GetDialogItemText( item, name ); + + // get resource type + Str255 string; + GetDialogItem( dialog, 4, &itemType, &item, &box ); + GetDialogItemText( item, string ); + if( *string != sizeof(ResType) ) + { + DisplayError( "\pInvalid resource type given", "\pYou should either choose a type from the pop-up menu, or enter a four character resource type of your own." ); + SetPort( oldPort ); + DisposeDialog( dialog ); + return paramErr; + } + BlockMoveData( string +1, &type, sizeof(ResType) ); + + // get resource ID + long number; + GetDialogItem( dialog, 6, &itemType, &item, &box ); + GetDialogItemText( item, string ); + StringToNum( string, &number ); + if( number < -32768 || number > 32767 ) + { + DisplayError( "\pInvalid resource ID chosen", "\pYou have to pick a number between -32768 and +32767. All ID numbers less than 128 are reserved by Apple." ); + SetPort( oldPort ); + DisposeDialog( dialog ); + return paramErr; + } + resID = (short) number; + + // create resource + error = CreateNewResource( name, type, resID, attributes ); + } + SetPort( oldPort ); + DisposeDialog( dialog ); + return error; +} + +/*** CREATE NEW RESOURCE ***/ +OSStatus FileWindow::CreateNewResource( ConstStr255Param name, ResType type, SInt16 resID, SInt16 attributes ) +{ + // cycle through ResourceObject chain + OSStatus error = noErr; + ResourceObjectPtr last = resourceMap, addition = (ResourceObjectPtr) NewPtrClear( sizeof(ResourceObject) ); + while( last->next ) last = last->next; + + // append new resource to chain + last->next = addition; + addition->file = this; + addition->number = last->number + 1; + addition->data = NewHandleClear( 0 ); + BlockMoveData( name, addition->name, sizeof(Str255) ); + addition->size = 0; + addition->type = type; + addition->resID = resID; + addition->attribs = attributes; + + // update the file's resource counts + numResources += 1; + if( GetResourceCount(type) == 0 ) + numTypes += 1; + +#if TARGET_API_MAC_CARBON + // add the resource to the databrowser + error = AddDataBrowserItems( dataBrowser, kDataBrowserNoItem, 1, &addition->number, kDataBrowserItemNoProperty ); +#endif + + // mark file as dirty + fileDirty = true; + SetWindowModified( window, fileDirty ); + + return error; +} + +/*** OPEN RESOURCE ***/ +OSStatus FileWindow::OpenResource( DataBrowserItemID itemID, MenuCommand command ) +{ + #pragma unused( command ) + // get opened resource + OSStatus error = noErr; + Boolean stop = false; + ResourceObjectPtr resource = GetResource( itemID ); + + // check for null resource + if( resource == null ) + { + DisplayError( "\pYou just double-clicked on a non-existant resource!" ); + return paramErr; + } + + // open correct editor + switch( command ) + { + case kMenuCommandOpenHex: + LoadEditor( resource, "\pHex Editor" ); + break; + + case kMenuCommandOpenDefault: + Str255 typeStr; + TypeToPString( resource->Type(), typeStr ); + AppendPString( typeStr, "\p Editor" ); + LoadEditor( resource, typeStr ); + break; + + case kMenuCommandOpenTemplate: + LoadEditor( resource, "\pTemplate Editor" ); + error = eventNotHandledErr; + break; + + case kMenuCommandOpenSpecific: + // bug: display template selection dialog here + error = eventNotHandledErr; + break; + + default: + DebugError( "\pWrong command sent to FileWindow::OpenResource()" ); + error = eventNotHandledErr; + break; + } + return error; +} + +/*** DISPOSE RESOURCE MAP ***/ +OSStatus FileWindow::DisposeResourceMap() +{ + if( !resourceMap ) return noErr; // resource map already disposed + + UInt32 numDeleted = 0; + ResourceObjectPtr current = resourceMap, next; + while( current ) + { + next = current->Next(); + if( current->nameIconRgn ) DisposeRgn( current->nameIconRgn ); + if( current->data ) DisposeHandle( current->data ); + DisposePtr( (Ptr) current ); +// delete current; + numDeleted += 1; + current = next; + } + if( numResources != numDeleted ) return paramErr; // my lazy way of saying I don't know what happened + else return noErr; +} + + /******************/ + /* SOUND ROUTINES */ +/******************/ + +/*** PLAY SOUND ***/ +OSStatus FileWindow::PlaySound( DataBrowserItemID itemID ) +{ + long refNum = 0; // unused + ResourceObjectPtr resource = GetResource( itemID ); + if( resource->Data() ) + { + SInt8 state = HGetState( resource->Data() ); + HLock( resource->Data() ); + if( g.asyncSound ) SHPlayByHandle( resource->Data(), &refNum ); + else SndPlay( nil, (SndListHandle) resource->Data(), false ); + HSetState( resource->Data(), state ); + } + return noErr; +} + + /*******************************/ + /* VARIABLE ACCESSOR FUNCTIONS */ +/*******************************/ + +/*** GET FILE SPEC ***/ +FSSpecPtr FileWindow::GetFileSpec( void ) +{ + return fileSpec; +} + +/*** SET FILE SPEC ***/ +void FileWindow::SetFileSpec( FSSpecPtr spec ) +{ + fileExists = spec? true:false; + if( fileExists ) + { + BlockMoveData( (Ptr) spec, (Ptr) fileSpec, sizeof(FSSpec) ); + SetWindowProxyFSSpec( window, fileSpec ); + SetWindowModified( window, fileDirty ); + } + else + { + DisposePtr( (Ptr) fileSpec ); + fileSpec = (FSSpecPtr) NewPtrClear( sizeof(FSSpec) ); + SetWindowProxyCreatorAndType( window, kResKnifeCreator, kResourceFileType, kOnSystemDisk ); + SetWindowModified( window, true ); + } +} + +/*** IS FILE DIRTY ***/ +Boolean FileWindow::IsFileDirty( void ) +{ + return fileDirty; +} + +/*** SET FILE DIRTY ***/ +void FileWindow::SetFileDirty( Boolean dirty ) +{ + fileDirty = dirty; // bug: used to crash my machine but mysteriously doesn't any more + SetWindowModified( window, dirty ); +} + +#if TARGET_API_MAC_CARBON + +/*** GET DATA BROWSER ***/ +ControlRef FileWindow::GetDataBrowser( void ) +{ + return dataBrowser; +} + +#endif + + /**********************/ + /* RESOURCE ACCESSORS */ +/**********************/ + +/*** GET RESOURCE COUNT ***/ +UInt32 FileWindow::GetResourceCount( ResType wanted ) +{ + #pragma unused( wanted ) + return numResources; +} + +/*** GET RESOURCE ***/ +ResourceObjectPtr FileWindow::GetResource( DataBrowserItemID itemID ) +{ + ResourceObjectPtr res = resourceMap; + if( itemID == kDataBrowserNoItem ) return null; + while( itemID != res->Number() ) + { + res = res->Next(); + if( res == null ) return null; + } + return res; +} + +/*** GET RESOURCE NAME ***/ +UInt8* FileWindow::GetResourceName( DataBrowserItemID itemID ) +{ + ResourceObjectPtr resource = GetResource( itemID ); + if( resource == null ) return null; + return resource->Name(); +} + +/*** GET RESOURCE SIZE ***/ +UInt32 FileWindow::GetResourceSize( DataBrowserItemID itemID ) +{ + ResourceObjectPtr resource = GetResource( itemID ); + if( resource == null ) return null; + return resource->Size(); +} + +/*** GET RESOURCE TYPE ***/ +ResType FileWindow::GetResourceType( DataBrowserItemID itemID ) +{ + ResourceObjectPtr resource = GetResource( itemID ); + if( resource == null ) return null; + return resource->Type(); +} + +/*** GET RESOURCE ID ***/ +SInt16 FileWindow::GetResourceID( DataBrowserItemID itemID ) +{ + ResourceObjectPtr resource = GetResource( itemID ); + if( resource == null ) return null; + return resource->ID(); +} + +/*** GET RESOURCE ATTRIBUTES ***/ +SInt16 FileWindow::GetResourceAttributes( DataBrowserItemID itemID ) +{ + ResourceObjectPtr resource = GetResource( itemID ); + if( resource == null ) return null; + return resource->Attributes(); +} \ No newline at end of file diff --git a/Carbon/Classes/FileWindow.h b/Carbon/Classes/FileWindow.h new file mode 100644 index 0000000..1333871 --- /dev/null +++ b/Carbon/Classes/FileWindow.h @@ -0,0 +1,179 @@ +#include "ResKnife.h" +#include "WindowObject.h" + +#ifndef _ResKnife_FileWindow_ +#define _ResKnife_FileWindow_ + +/*! + @header FileWindow + @discussion The bulk of the ResKnife code is concerned with the managment of this window. It is the essence of the program. +*/ + +/* Constants */ +const UInt16 kDefaultHeaderHeight = 20; +const UInt16 kMinimumFileWindowWidth = 128; +const UInt16 kDefaultFileWindowWidth = 420; +const UInt16 kMinimumFileWindowHeight = 128 + kDefaultHeaderHeight; +const UInt16 kDefaultFileWindowHeight = 384 + kDefaultHeaderHeight; +const UInt16 kBevelButtonHeight = 20; +const UInt16 kFileWindowHeaderHeight = kDefaultHeaderHeight + kBevelButtonHeight; +const UInt16 kFileWindowRowHeight = 19; +const UInt16 kFileWindowTextHeight = kFileWindowRowHeight - 5; + +const UInt16 kFileWindowMinimumNameColumnWidth = 150; +const UInt16 kFileWindowDefaultNameColumnWidth = 250; +const UInt16 kFileWindowTypeColumnWidth = 52; +const UInt16 kFileWindowIDColumnWidth = 52; +const UInt16 kFileWindowSizeColumnWidth = 52; +const UInt16 kFileWindowAttributesColumnWidth = 120; +const UInt16 kFileWindowSortColumnWidth = 16; +const UInt16 kFileWindowAllOtherColumnWidths = kFileWindowTypeColumnWidth + kFileWindowIDColumnWidth + kFileWindowSizeColumnWidth + kFileWindowAttributesColumnWidth +8; // excludes variable with name column + +const UInt16 kFileWindowNameColumnTextOffset = 42; // start of text, actual column starts at zero +const UInt16 kFileWindowTypeColumnOffset = 0; // offset from end of name column +const UInt16 kFileWindowIDColumnOffset = kFileWindowTypeColumnOffset + kFileWindowTypeColumnWidth; +const UInt16 kFileWindowSizeColumnOffset = kFileWindowIDColumnOffset + kFileWindowIDColumnWidth; +const UInt16 kFileWindowAttributesColumnOffset = kFileWindowSizeColumnOffset + kFileWindowSizeColumnWidth; + +const UInt32 kWindowPropertyDataBrowser = FOUR_CHAR_CODE('brow'); +const UInt32 kHeaderSignature = FOUR_CHAR_CODE('head'); +const UInt32 kLeftTextSignature = FOUR_CHAR_CODE('left'); +const UInt32 kRightTextSignature = FOUR_CHAR_CODE('rght'); +const UInt32 kDataBrowserSignature = FOUR_CHAR_CODE('brow'); +const UInt32 kDataBrowserNameColumn = FOUR_CHAR_CODE('name'); +const UInt32 kDataBrowserTypeColumn = FOUR_CHAR_CODE('type'); +const UInt32 kDataBrowserIDColumn = FOUR_CHAR_CODE('id '); +const UInt32 kDataBrowserSizeColumn = FOUR_CHAR_CODE('size'); + +typedef enum +{ + kSortName = 1, + kSortType, + kSortID, + kSortSize, + kSortAttrs +} SortOrder; + +/*** FILE WINDOW CLASS ***/ +class FileWindow : WindowObject +{ + // file info + FSSpecPtr fileSpec; // user's copy + FSSpecPtr tempSpec; // my copy + Boolean fileExists; // existing file was opened or file has been saved at some point + Boolean fileDirty; // temp file ­ real file => save available + Boolean rfBased; // file's resource map came from it's resource fork + + // resource info + UInt16 numTypes; + UInt32 numResources; +#if !TARGET_API_MAC_CARBON + UInt32 numSelected; // for fake data browser +#endif + Handle dataFork; // bug: what is this for? + ResourceObjectPtr resourceMap; + + // controls +#if TARGET_API_MAC_CARBON + ControlRef dataBrowser; // header controls in WindowObject +#else // make a fake databrowser + SortOrder sortOrder; // the one which is actually selected + ControlRef sortName; + ControlRef sortType; + ControlRef sortID; + ControlRef sortSize; + ControlRef sortAttrs; + ControlRef sortDir; + SInt16 nameColumnWidth; // varies with window size, and needs to be signed +#endif + + /* methods */ +public: + FileWindow( FSSpecPtr spec = null ); + virtual ~FileWindow( void ); + // overridden inherited functions +/*! + * @function Window + * @discussion Accessor for the object’s WindowRef. + */ + virtual WindowRef Window( void ); + virtual OSStatus BoundsChanging( EventRef event ); + virtual OSStatus BoundsChanged( EventRef event ); +#if !TARGET_API_MAC_CARBON + virtual OSStatus Activate( Boolean active = true ); + virtual OSStatus Update( RgnHandle region = null ); + virtual OSStatus Click( Point mouse, EventModifiers modifiers ); + + // drawing +private: + virtual OSStatus UpdateScrollBars( void ); + OSStatus DrawResourceIcon( ResourceObjectPtr resource, UInt16 line ); + + // fake data brwser + OSStatus ClearSelection( void ); +#endif + + // file manipulation +public: + OSStatus ReadResourceFork( void ); + OSStatus ReadDataFork( OSStatus rfError ); + OSStatus InitDataBrowser( void ); + OSStatus SaveFile( FSSpecPtr saveSpec = null ); +private: +/*! + @function ReadResourceMap + @discussion Requires the fork containing resources to be at the top of the resource chain +*/ + OSStatus ReadResourceMap( void ); // fork-independent resource routines + OSStatus SaveResourceMap( void ); + + // carbon routines +public: + OSStatus Zoomed( EventRef event ); + OSStatus SetIdealSize( EventRef event ); + OSStatus DisplaySaveDialog( void ); + OSStatus DisplayModelessAskSaveChangesDialog( void ); + OSStatus DisplaySaveAsDialog( void ); + OSStatus DisplayModelessPutFileDialog( void ); + OSStatus DisplayRevertFileDialog( void ); + OSStatus DisplayModelessAskDiscardChangesDialog( void ); + OSStatus DisplayNewResourceDialog( void ); + OSStatus DisplayNewResourceSheet( void ); + + // resource map processing + OSStatus CreateNewResource( ConstStr255Param name, ResType type, SInt16 resID, SInt16 attribs ); + OSStatus OpenResource( DataBrowserItemID itemID, MenuCommand command ); +private: + OSStatus DisposeResourceMap( void ); + +public: + // sound handlers + OSStatus PlaySound( DataBrowserItemID itemID ); + + // file accessors + FSSpecPtr GetFileSpec( void ); + void SetFileSpec( FSSpecPtr spec ); + Boolean IsFileDirty( void ); + void SetFileDirty( Boolean dirty = true ); +#if TARGET_API_MAC_CARBON + ControlRef GetDataBrowser( void ); +#endif + + // resource accessors + UInt32 GetResourceCount( ResType wanted = 0x00000000 ); + ResourceObjectPtr GetResource( DataBrowserItemID itemID ); + UInt8* GetResourceName( DataBrowserItemID itemID ); + UInt32 GetResourceSize( DataBrowserItemID itemID ); + ResType GetResourceType( DataBrowserItemID itemID ); + SInt16 GetResourceID( DataBrowserItemID itemID ); + SInt16 GetResourceAttributes( DataBrowserItemID itemID ); +}; + +/* window event handler */ +pascal void FileWindowScrollAction( ControlHandle control, SInt16 controlPart ); +pascal OSStatus FileWindowEventHandler( EventHandlerCallRef callRef, EventRef event, void *userData ); +pascal OSStatus FileWindowUpdateMenus( EventHandlerCallRef callRef, EventRef event, void *userData ); +pascal OSStatus FileWindowParseMenuSelection( EventHandlerCallRef callRef, EventRef event, void *userData ); +pascal OSStatus NewResourceEventHandler( EventHandlerCallRef callRef, EventRef event, void *userData ); + +#endif \ No newline at end of file diff --git a/Carbon/Classes/Files.cpp b/Carbon/Classes/Files.cpp new file mode 100644 index 0000000..a9ef20c --- /dev/null +++ b/Carbon/Classes/Files.cpp @@ -0,0 +1,860 @@ +#include "Files.h" +#include "Application.h" +#include "FileWindow.h" +#include "ResourceObject.h" // for saving inital resource data +#include "DataBrowser.h" // for kDataBrowserForkItem constant +#include "Utility.h" +#include "Errors.h" +extern globals g; + +/* Convert an FSRef to an FSSpec: + FSGetCatalogInfo( &fsref, kFSCatInfoNone, null, null, &spec, null ); + Get your application's FSSpec + FSSpec spec; + String name; + ProcessInfoRec info; + ProcessSerialNumber psn; + GetCurrentProcess( &psn ); + info.processName = &name; + info.processAppSpec = &spec; + GetProcessInformation( psn, &info ); +*/ + +/*** NAV OPEN FILE ***/ +/*OSStatus NavOpenFile( void ) +{ + OSStatus error = noErr; + NavReplyRecord reply; + NavDialogOptions dialogOptions; + NavEventUPP eventProc = NewNavEventUPP( NavEventFilter ); + NavPreviewUPP previewProc = NewNavPreviewUPP( NavPreviewFilter ); + NavObjectFilterUPP filterProc = NewNavObjectFilterUPP( NavFileFilter ); + + // Initialize dialog options structure and set default values + NavGetDefaultDialogOptions( &dialogOptions ); + dialogOptions.dialogOptionFlags = kNavNoTypePopup | kNavDontAutoTranslate | kNavDontAddTranslateItems | kNavAllowMultipleFiles | kNavAllowInvisibleFiles; + BlockMoveData( g.appName, dialogOptions.clientName, sizeof(Str255) ); + + // call the nav services routine + error = NavGetFile( null, &reply, &dialogOptions, eventProc, previewProc, filterProc, null, null ); + DisposeNavEventUPP( eventProc ); + DisposeNavPreviewUPP( previewProc ); + DisposeNavObjectFilterUPP( filterProc ); + + if( reply.validRecord && error == noErr ) + { + if( g.useAppleEvents ) + error = AppleEventSendSelf( kCoreEventClass, kAEOpenDocuments, reply.selection ); +#if !TARGET_API_MAC_CARBON + else + { + // open the list of item(s): + AEKeyword keyword; + DescType descType; + FSSpec fileSpec; + Size actualSize; + + error = AEGetNthPtr( &(reply.selection), 1, typeFSS, &keyword, &descType, &fileSpec, sizeof(FSSpec), &actualSize ); + if( !error ) // if sucessful, open & read the file + new FileWindow( &fileSpec ); +*/ /* SInt32 count; + FSSpec fileSpec; + AEDesc resultDesc; + error = AECountItems( &(reply.selection), &count ); + if( !error ) + for( SInt32 n = 1; n <= count; n++ ) + { + error = AEGetNthDesc( &(reply.selection), n, typeFSS, null, &resultDesc ); + if( !error ) + { + HLock( (Handle) resultDesc.dataHandle ); + BlockMoveData( (void *) *resultDesc.dataHandle, &fileSpec, sizeof(FSSpec) ); + new FileWindow( &fileSpec ); + AEDisposeDesc( &resultDesc ); + } + } +*/ /* } +#endif + + // Always dispose of reply structure, resources, and descriptors + error = NavDisposeReply( &reply ); + } + return error; +} +*/ + +#if !TARGET_API_MAC_CARBON + +/*** OPEN FILE ***/ +OSStatus OpenFile( short vRefNum, long dirID, ConstStr255Param fileName ) +{ + FSSpec fileSpec; + FSMakeFSSpec( vRefNum, dirID, fileName, &fileSpec ); + new FileWindow( &fileSpec ); + return noErr; +} + +/*** DISPLAY STANDARD FILE OPEN DIALOG ***/ +OSStatus DisplayStandardFileOpenDialog( void ) +{ + StandardFileReply theReply; + SFTypeList typeList = { 0x0L }; + + StandardGetFile( null, 0, typeList, &theReply ); + if( theReply.sfGood ) new FileWindow( &theReply.sfFile ); + return theReply.sfGood? noErr:userCanceledErr; +} + +#endif + +/*** OPEN A FILE DIALOG ***/ +OSStatus DisplayOpenDialog( void ) +{ + OSStatus error = noErr; + NavReplyRecord reply; + NavDialogOptions dialogOptions; + NavEventUPP eventProc = NewNavEventUPP( NavEventFilter ); + NavPreviewUPP previewProc = NewNavPreviewUPP( NavPreviewFilter ); + NavObjectFilterUPP filterProc = NewNavObjectFilterUPP( NavFileFilter ); + NavTypeListHandle typeList = null; + + NavGetDefaultDialogOptions( &dialogOptions ); + dialogOptions.dialogOptionFlags += kNavNoTypePopup; + GetIndString( dialogOptions.clientName, kFileNameStrings, kStringResKnifeName ); + error = NavGetFile( null, &reply, &dialogOptions, eventProc, previewProc, filterProc, typeList, null); + if( reply.validRecord || !error ) + { + AEKeyword keyword; + DescType descType; + FSSpec fileSpec; + Size actualSize; + + error = AEGetNthPtr( &(reply.selection), 1, typeFSS, &keyword, &descType, &fileSpec, sizeof(FSSpec), &actualSize ); + if( !error ) new FileWindow( &fileSpec ); // if sucessful, opens & reads the file + NavDisposeReply( &reply ); + } + else error = userCanceledErr; + + DisposeNavEventUPP( eventProc ); + DisposeNavPreviewUPP( previewProc ); + DisposeNavObjectFilterUPP( filterProc ); + return error; +} + +/*** DISPLAY MODELESS GET FILE DIALOG ***/ +OSStatus DisplayModelessGetFileDialog( void ) +{ + OSStatus error; + NavEventUPP eventProc = NewNavEventUPP( ModelessGetFileHandler ); + NavPreviewUPP previewProc = null; + NavObjectFilterUPP filterProc = null; + NavTypeListHandle typeList = null; + NavDialogCreationOptions options; + error = NavGetDefaultDialogCreationOptions( &options ); + options.clientName = (CFStringRef) CFBundleGetValueForInfoDictionaryKey( CFBundleGetMainBundle(), kCFBundleNameKey ); + + NavDialogRef dialog; + error = NavCreateGetFileDialog( &options, typeList, eventProc, previewProc, filterProc, null, &dialog ); + error = NavDialogRun( dialog ); + if( error ) NavDialogDispose( dialog ); + return error; +} + +/*** SAVE FILE DIALOG ***/ +OSStatus FileWindow::DisplaySaveDialog( void ) +{ + OSStatus error = noErr; + NavDialogOptions options; + NavEventUPP eventProc = NewNavEventUPP( NavEventFilter ); + NavAskSaveChangesAction action = g.quitting? kNavSaveChangesQuittingApplication : kNavSaveChangesClosingDocument; + NavAskSaveChangesResult result; + + NavGetDefaultDialogOptions( &options ); + GetWindowTitle( window, options.savedFileName ); +// GetIndString( options.clientName, kFileNameStrings, kStringAppName ); + NavAskSaveChanges( &options, action, &result, eventProc, null ); + + switch( result ) + { + case kNavAskSaveChangesSave: + if( fileExists ) error = SaveFile( null ); + else error = DisplaySaveAsDialog(); + break; + + case kNavAskSaveChangesDontSave: + break; + + case kNavAskSaveChangesCancel: + g.cancelQuit = true; + g.quitting = false; // bug: why can't I check for userCanceledErr instead? + error = userCanceledErr; + break; + } + + DisposeNavEventUPP( eventProc ); + return error; +} + +/*** DISPLAY MODELESS SAVE DIALOG ***/ +OSStatus FileWindow::DisplayModelessAskSaveChangesDialog( void ) +{ + OSStatus error; + NavEventUPP eventProc = NewNavEventUPP( ModelessAskSaveChangesHandler ); + NavPreviewUPP previewProc = null; + NavObjectFilterUPP filterProc = null; + NavTypeListHandle typeList = null; + NavAskSaveChangesAction action = g.quitting? kNavSaveChangesQuittingApplication : kNavSaveChangesClosingDocument; + NavDialogCreationOptions options; + error = NavGetDefaultDialogCreationOptions( &options ); + options.parentWindow = window; + options.modality = kWindowModalityWindowModal; + options.clientName = (CFStringRef) CFBundleGetValueForInfoDictionaryKey( CFBundleGetMainBundle(), kCFBundleNameKey ); // bug: are these two strings CFReleased? Should they be? + options.saveFileName = CFStringCreateWithPascalString( null, fileSpec->name, CFStringGetSystemEncoding()); // bug: see above + + NavDialogRef dialog; + error = NavCreateAskSaveChangesDialog( &options, action, eventProc, this, &dialog ); + error = NavDialogRun( dialog ); + return error; +} + +/*** SAVE AS DIALOG ***/ +OSStatus FileWindow::DisplaySaveAsDialog( void ) +{ + OSStatus error = noErr; + NavReplyRecord reply; + NavDialogOptions dialogOptions; + NavEventUPP eventProc = NewNavEventUPP( NavEventFilter ); + + NavGetDefaultDialogOptions( &dialogOptions ); + GetWindowTitle( window, dialogOptions.savedFileName ); + GetIndString( dialogOptions.clientName, kFileNameStrings, kStringResKnifeName ); + error = NavPutFile( null, &reply, &dialogOptions, eventProc, kResourceFileType, kResKnifeCreator, null ); + if( reply.validRecord || !error ) + { + AEKeyword keyword; + DescType descType; + FSSpec savedSpec; + Size actualSize; + + // bug: does the next line only get the first selected file? + error = AEGetNthPtr( &(reply.selection), 1, typeFSS, &keyword, &descType, &savedSpec, sizeof(FSSpec), &actualSize ); + if( !error ) + { + if( reply.replacing ) error = FSpDelete( &savedSpec ); + if( !error ) + { + error = SaveFile( &savedSpec ); + if ( !error ) + error = NavCompleteSave( &reply, kNavTranslateInPlace ); + } + else if( error == fBsyErr ) + { + DisplayError( kStringUnknownError, kExplanationUnknownError ); // read error + } + } + NavDisposeReply( &reply ); + } + else if( !error ) error = userCanceledErr; + + DisposeNavEventUPP( eventProc ); + return error; +} + +/*** DISPLAY MODELESS PUT FILE DIALOG ***/ +OSStatus FileWindow::DisplayModelessPutFileDialog( void ) +{ + OSStatus error; + NavEventUPP eventProc = NewNavEventUPP( ModelessPutFileHandler ); + NavDialogCreationOptions options; + error = NavGetDefaultDialogCreationOptions( &options ); + options.parentWindow = window; + options.modality = kWindowModalityWindowModal; + options.clientName = (CFStringRef) CFBundleGetValueForInfoDictionaryKey( CFBundleGetMainBundle(), kCFBundleNameKey ); + options.saveFileName = CFStringCreateWithPascalString( null, fileSpec->name, CFStringGetSystemEncoding()); + + NavDialogRef dialog; + error = NavCreatePutFileDialog( &options, kResourceFileType, kResKnifeCreator, eventProc, this, &dialog ); + error = NavDialogRun( dialog ); + return error; +} + +/*** DISPLAY REVERT FILE DIALOG ***/ +OSStatus FileWindow::DisplayRevertFileDialog( void ) +{ + OSStatus error = noErr; + NavDialogOptions dialogOptions; + NavEventUPP eventProc = NewNavEventUPP( NavEventFilter ); + NavAskDiscardChangesResult result; + + NavGetDefaultDialogOptions( &dialogOptions ); + GetWindowTitle( window, dialogOptions.savedFileName ); + NavAskDiscardChanges( &dialogOptions, &result, eventProc, null ); + + switch( result ) + { + case kNavAskDiscardChanges: +/* error = CloseFile(); + if( error ) break; + + error = OpenFile(); + if( error ) break; + + error = ReadFile(); +*/ break; + + case kNavAskDiscardChangesCancel: + break; + } + + DisposeNavEventUPP( eventProc ); + return error; +} + +/*** DISPLAY MODELESS ASK DISCARD CHANGES DIALOG ***/ +OSStatus FileWindow::DisplayModelessAskDiscardChangesDialog( void ) +{ + OSStatus error; + NavEventUPP eventProc = NewNavEventUPP( ModelessAskDiscardChangesHandler ); + NavDialogCreationOptions options; + error = NavGetDefaultDialogCreationOptions( &options ); + options.parentWindow = window; + options.modality = kWindowModalityWindowModal; + options.clientName = (CFStringRef) CFBundleGetValueForInfoDictionaryKey( CFBundleGetMainBundle(), kCFBundleNameKey ); + options.saveFileName = CFStringCreateWithPascalString( null, fileSpec->name, CFStringGetSystemEncoding()); + + NavDialogRef dialog; + error = NavCreateAskDiscardChangesDialog( &options, eventProc, this, &dialog ); + error = NavDialogRun( dialog ); + return error; +} + + /*******************************/ + /* NAV SERVICES EVENT HANDLERS */ +/*******************************/ + +#pragma mark - + +/*** NAV SERVICES EVENT FILTER ***/ +pascal void NavEventFilter( NavEventCallbackMessage callBackSelector, NavCBRecPtr cbRecord, NavCallBackUserData callBackUD ) +{ + #pragma unused( callBackUD ) + switch( callBackSelector ) + { + case kNavCBEvent: + switch( cbRecord->eventData.eventDataParms.event->what ) + { + default: + break; + } + break; + } +} + +/*** NAV SERVICES PREVIEW FILTER ***/ +pascal Boolean NavPreviewFilter( NavCBRecPtr callBackParms, void *callBackUD ) +{ + #pragma unused( callBackParms, callBackUD ) + return false; +} + +/*** NAV SERVICES FILE FILTER ***/ +pascal Boolean NavFileFilter( AEDescPtr theItem, void *info, void *callBackUD, NavFilterModes filterMode ) +{ + #pragma unused( theItem, info, callBackUD, filterMode ) +/* do something useful here: + count rsources & types + give DF-based or RF-based info + maybe something else? +*/ return true; +} + +#pragma mark - + +/*** MODELESS GET FILE HANDLER ***/ +pascal void ModelessGetFileHandler( const NavEventCallbackMessage callBackSelector, NavCBRecPtr cbRecord, NavCallBackUserData callBackUD ) +{ + #pragma unused( callBackUD ) + OSStatus error = noErr; + switch( callBackSelector ) + { + case kNavCBAccept: +// case kNavCBUserAction: + { // open first selected file + NavReplyRecord reply; + error = NavDialogGetReply( cbRecord->context, &reply ); + if( reply.validRecord ) + { + AEKeyword keyword; + DescType descType; + FSSpec fileSpec; + Size actualSize; + + error = AEGetNthPtr( &(reply.selection), 1, typeFSS, &keyword, &descType, &fileSpec, sizeof(FSSpec), &actualSize ); + if( !error ) new FileWindow( &fileSpec ); // if sucessful, opens & reads the file + } + else SysBeep(0); + NavDisposeReply( &reply ); + } break; + + case kNavCBTerminate: + { // dispose of the dialog + NavDialogDispose( cbRecord->context ); + } break; + } +} + +/*** MODELESS ASK SAVE CHANGES HANDLER ***/ +pascal void ModelessAskSaveChangesHandler( const NavEventCallbackMessage callBackSelector, NavCBRecPtr cbRecord, NavCallBackUserData callBackUD ) +{ + OSStatus error = noErr; + FileWindowPtr file = (FileWindowPtr) callBackUD; + switch( callBackSelector ) + { + case kNavCBUserAction: + { // open first selected file + NavReplyRecord reply; + error = NavDialogGetReply( cbRecord->context, &reply ); + if( reply.validRecord ) + { + error = file->SaveFile( null ); + } + else SysBeep(0); + NavDisposeReply( &reply ); + } break; + + case kNavCBTerminate: + { // dispose of the dialog + NavDialogDispose( cbRecord->context ); + } break; + } +} + +/*** MODELESS PUT FILE HANDLER ***/ +pascal void ModelessPutFileHandler( const NavEventCallbackMessage callBackSelector, NavCBRecPtr cbRecord, NavCallBackUserData callBackUD ) +{ + OSStatus error = noErr; + FileWindowPtr file = (FileWindowPtr) callBackUD; + switch( callBackSelector ) + { + case kNavCBUserAction: + { // open first selected file + NavReplyRecord reply; + error = NavDialogGetReply( cbRecord->context, &reply ); + if( reply.validRecord ) + { + ; + } + else SysBeep(0); + NavDisposeReply( &reply ); + } break; + + case kNavCBTerminate: + { // dispose of the dialog + NavDialogDispose( cbRecord->context ); + } break; + } +} + +/*** MODELESS ASK DISCARD CHANGES HANDLER ***/ +pascal void ModelessAskDiscardChangesHandler( const NavEventCallbackMessage callBackSelector, NavCBRecPtr cbRecord, NavCallBackUserData callBackUD ) +{ + OSStatus error = noErr; + FileWindowPtr file = (FileWindowPtr) callBackUD; + switch( callBackSelector ) + { + case kNavCBUserAction: + { // open first selected file + NavReplyRecord reply; + error = NavDialogGetReply( cbRecord->context, &reply ); + if( reply.validRecord ) + { + ; + } + else SysBeep(0); + NavDisposeReply( &reply ); + } break; + + case kNavCBTerminate: + { // dispose of the dialog + NavDialogDispose( cbRecord->context ); + } break; + } +} + + /*********************/ + /* READING & WRITING */ +/*********************/ + +#pragma mark - + +/*** READ RESOURCE FORK ***/ +OSStatus FileWindow::ReadResourceFork( void ) +{ + // open file for reading + OSStatus error = noErr; + SInt16 oldResFile = CurResFile(); + SetResLoad( false ); // don't load "preload" resources + SInt16 refNum = FSpOpenResFile( fileSpec, fsRdPerm ); + SetResLoad( true ); + if( !refNum ) return resFNotFound; + UseResFile( refNum ); + error = ResError(); + if( error ) // no resource map in resource fork, try in data fork before alerting user + DebugError( "\pResource map not present in resource fork", error ); + // fork-independant resource reading routine + else error = ReadResourceMap(); + rfBased = error? false:true; + + // tidy up loose ends + UseResFile( oldResFile ); + FSClose( refNum ); + return error; +} + +/*** READ DATA FORK ***/ +OSStatus FileWindow::ReadDataFork( OSStatus rfError ) +{ + OSStatus error = rfError; + if( error ) // error occoured reading resource map from resource fork, try reading map from data fork instead + { +#if TARGET_API_MAC_CARBON + FSRef fileRef; + SInt16 refNum; + SInt16 oldResFile = CurResFile(); + error = FSpMakeFSRef( fileSpec, &fileRef ); + if( error ) + { + DebugError( "\pFSpMakeFSRef error", error ); + return error; + } + if( FSOpenResourceFile == (void *) kUnresolvedCFragSymbolAddress ) + { + DisplayError( "\pCarbonLib version too old", "\pThe version of CarbonLib you have installed won't let you view files whose resources are stored in the data fork. Please update to version 1.3 GM of CarbonLib, available from http://www.apple.com/" ); + error = paramErr; + return error; + } + SetResLoad( false ); // don't load "preload" resources + error = FSOpenResourceFile( &fileRef, 0, null, fsRdPerm, &refNum ); + SetResLoad( true ); + if( error || !refNum ) + { + DisplayError( "\pThis file is corrupt", "\pSorry, but you will not be able to open it. You should replace it with a backÑup. FSOpenResourceFile()" ); + return error? error:resFNotFound; + } + UseResFile( refNum ); + + // fork-independant resource reading routine + error = ReadResourceMap(); + + // tidy up loose ends + UseResFile( oldResFile ); + FSClose( refNum ); +#endif + return error; + } + else // no error occoured reading resource map from resource fork, read data fork as byte stream + { + // open file for reading + SInt16 refNum; + error = FSpOpenDF( fileSpec, fsRdPerm, &refNum ); + if( error ) + { + DisplayError( "\pData fork could not be read", "\pThis file appears to be corrupted. Although the resources could be read in correctly, the data fork could not be found. Please run Disk First Aid to correct the problem." ); + return error; + } + ResourceObjectPtr current = (ResourceObjectPtr) NewPtrClear( sizeof(ResourceObject) ); + if( !current ) + { + DisplayError( "\pNot enough memory to read data fork", "\pPlease quit other applications and try again." ); + FSClose( refNum ); + return error; + } + + current->number = kDataBrowserDataForkItem; // ID of fork in dataBrowser + *current->name = 0x00; + current->type = 0x00000000; + current->resID = 0; + GetEOF( refNum, ¤t->size ); + current->attribs = 0; + current->nameIconRgn = NewRgn(); + current->file = this; + current->dataFork = true; + + // get new handle + current->retainCount = 1; + current->data = NewHandleClear( current->size ); + if( !current->data || MemError() ) + { + DisplayError( "\pNot enough memory to read data fork", "\pPlease quit other applications and try again." ); + FSClose( refNum ); + return memFullErr; + } + + // read data fork + HLock( current->data ); + error = FSRead( refNum, ¤t->size, *current->data ); + HUnlock( current->data ); + if( error ) + { + DisplayError( "\pFailed to read data fork.", "\pA mysterious error occured reading the data fork. The record saying how long the file is has probably been corrupted. You should run Disk First Aid to repair the dis." ); + FSClose( refNum ); + return error; + } + + current->next = resourceMap; + resourceMap = current; + FSClose( refNum ); + return error; + } +} + +/*** READ RESOURCE MAP ***/ +OSStatus FileWindow::ReadResourceMap( void ) +{ + OSStatus error = noErr; + + // set up variables & first resource record + numResources = 0; + numTypes = Count1Types(); + resourceMap = (ResourceObjectPtr) NewPtrClear( sizeof(ResourceObject) ); +// resourceMap = new ResourceObject( this ); + ResourceObjectPtr current = resourceMap; + + for( unsigned short i = 1; i <= numTypes; i++ ) + { + // read in each data type + ResType type; + Get1IndType( &type, i ); + UInt16 n = Count1Resources( type ); + for( UInt16 j = 1; j <= n; j++ ) + { + // get resource info +// SetResLoad( false ); + current->data = Get1IndResource( type, j ); + error = ResError(); +// SetResLoad( true ); + if( MemError() ) + { + DisplayError( "\pNot enough memory to read all resources", "\pPlease quit other applications and try again." ); + DisposePtr( (Ptr) current ); + return memFullErr; + } + if( !current->Data() || error != noErr ) + { + DisplayError( "\pResources are damaged, proceed with extreme caution!" ); + // bug: dialog should have "continue", "stop" and "quit" buttons, stop being default + DisposePtr( (Ptr) current ); + // delete current; + return error; // bug: what should I be doing here? + } + current->number = numResources + j; // ID of resource in dataBrowser + GetResInfo( current->Data(), ¤t->resID, ¤t->type, current->name ); + current->size = GetResourceSizeOnDisk( current->Data() ); + current->attribs = GetResAttrs( current->Data() ); + current->file = this; + current->dataFork = false; + DetachResource( current->Data() ); // bug: this needs to be here so calling AddResource() when saving will work, but if ResLoad() was off above, it will kill the only link between the Handle and the resource. + + if( i != numTypes || j != n ) // if this isn't the last resourceÉ + { + // Émove on to the next one + current->next = (ResourceObjectPtr) NewPtrClear( sizeof(ResourceObject) ); +// current->next = new ResourceObject( this ); + current = current->next; + } + } + numResources += n; + } + return error; +} + +/*** SAVE FILE ***/ +OSStatus FileWindow::SaveFile( FSSpecPtr saveSpec ) +{ + OSStatus error; + + if( saveSpec == null ) // we're straight saving the file, use a temp file, then switch + { + // set up file name + Str255 countStr; // bug: this is not initalised before being used + Str255 tempFileName = "\pResKnife Temporary File "; + NumToString( ++g.tempCount, (StringPtr) countStr ); + AppendPString( tempFileName, countStr ); + + // create temporary file spec + SInt32 dirID; + SInt16 vRefNum; // Always create the temporary file on the same volume as the file we're saving, otherwise FSpExchangeFiles() won't work + OSStatus error = FindFolder( fileSpec->vRefNum, /*kTemporaryFolderType*/ kDesktopFolderType, kCreateFolder, &vRefNum, &dirID ); + if( error ) DebugError( "\pFindFolder returned error.", error ); + error = FSMakeFSSpec( vRefNum, dirID, tempFileName, tempSpec ); + if( error == noErr ) + { + DisplayError( "\pFile already exists", "\pThe temporary file used by ResKnife to protect your data already exists, try saving again. If the problem persists, flush your temporary items folder with a utility such as Eradicator." ); + return error; + } + else if( error != fnfErr && error != dirNFErr ) + { + DebugError( "\pError calling FSMakeFSSpec from FileWindow::SaveFile.", error ); + return error; + } + } + else // we're doing a 'save as', no need for temp file + { // if we created one, then FSpExchange wouldn't work + tempSpec = saveSpec; + } + + // save plain DF if present + ResourceObjectPtr current = resourceMap; + if( rfBased && current->RepresentsDataFork() ) // requires data fork to be first item in list + { + // create data fork + FSpCreate( tempSpec, kResKnifeCreator, kResourceFileType, smSystemScript ); + + // open file for writing + SInt16 refNum; + error = FSpOpenDF( tempSpec, fsWrPerm, &refNum ); + if( error ) + { + DisplayError( "\pData fork could not be read", "\pThis file appears to be corrupted. Although the resources could be read in correctly, the data fork could not be found. Please run Disk First Aid to correct the problem." ); + return error; + } + + // save byte stream + SInt8 state = HGetState( resourceMap->Data() ); + HLock( current->Data() ); + SInt32 size = current->Size(); + SetEOF( refNum, size ); + error = FSWrite( refNum, &size, (Ptr) *current->Data() ); + HSetState( current->Data(), state ); + FSClose( refNum ); + } + else if( !rfBased && current->RepresentsDataFork() ) + { + DisplayError( "\pData fork present with DF-based resource file." ); + } + else if( rfBased ) + { + DisplayError( "\pTried to save resource fork based file, but no data fork could be found" ); + } + + // save resource map in specified fork + if( rfBased ) + { + SInt16 oldResFile = CurResFile(); + FSpCreateResFile( tempSpec, kResKnifeCreator, kResourceFileType, smSystemScript ); + SInt16 tempRef = FSpOpenResFile( tempSpec, fsWrPerm ); + UseResFile( tempRef ); + error = SaveResourceMap(); + UseResFile( oldResFile ); + CloseResFile( tempRef ); + error = ResError(); + if( error ) + { + DebugError( "\pError calling CloseResFile.", error ); + return error; + } + } + else + { + FSRef fileRef; + SInt16 refNum; + SInt16 oldResFile = CurResFile(); + error = FSpMakeFSRef( tempSpec, &fileRef ); + if( error ) + { + DebugError( "\pFSpMakeFSRef error", error ); + return error; + } + if( FSOpenResourceFile == (void *) kUnresolvedCFragSymbolAddress ) + { + DisplayError( "\pCarbonLib version too old", "\pThe version of CarbonLib you have installed won't let you save resources into the data fork. Please update to version 1.3.1 of CarbonLib, available from http://www.apple.com/" ); + error = paramErr; + return error; + } +/* error = FSCreateResourceFile( &fileRef, ); + const FSRef * parentRef, + UniCharCount nameLength, + const UniChar * name, + FSCatalogInfoBitmap whichInfo, + const FSCatalogInfo * catalogInfo, // can be NULL + UniCharCount forkNameLength, + const UniChar * forkName, // can be NULL + FSRef * newRef, // can be NULL + FSSpec * newSpec); // can be NULL + + if( error ) + { + DisplayError( "\pFile could not be created", "\pThe file to save your resources into could not be created. FSCreateResourceFile()" ); + return error? error:resFNotFound; + } +*/ error = FSOpenResourceFile( &fileRef, 0, null, fsRdPerm, &refNum ); + if( error || !refNum ) + { + DisplayError( "\pFile could not be created", "\pThe file to save your resources into could not be created. FSOpenResourceFile()" ); + return error? error:resFNotFound; + } + UseResFile( refNum ); + error = SaveResourceMap(); + UseResFile( oldResFile ); + CloseResFile( refNum ); + error = ResError(); + if( error ) + { + DebugError( "\pError calling CloseResFile.", error ); + return error; + } + } + + // switch file for temp if we did a regular save + if( saveSpec == null ) + { + // swap the temporary file for the real one + error = FSpExchangeFiles( tempSpec, fileSpec ); // bug: this will fail on non HFS/HFS+ file systems - therefore file will not save + if( error ) + { + DebugError( "\pError calling FSpExchangeFiles.", error ); + return error; + } + error = FSpDelete( tempSpec ); + if( error ) + { + DebugError( "\pError calling FSpDelete.", error ); + } + + // file is no longer dirty + fileDirty = false; + SetWindowModified( window, fileDirty ); + } + return error; +} + +/*** SAVE RESOURCE MAP ***/ +OSStatus FileWindow::SaveResourceMap( void ) +{ + OSStatus error = noErr; + // save resources from memory to the temp file + ResourceObjectPtr current = resourceMap; + if( current->RepresentsDataFork() == true ) + current = current->Next(); // skip data fork + while( current ) + { + // save resource + AddResource( current->Data(), current->Type(), current->ID(), current->Name() ); + if( ResError() == addResFailed ) + { + DisplayError( "\pSaving Failed", "\pCould not add resources to file." ); + current = null; + error = addResFailed; + } + else + { + SetResAttrs( current->Data(), current->Attributes() ); + ChangedResource( current->Data() ); + + // clean up & move on + DetachResource( current->Data() ); + current = current->Next(); + } + } + return error; +} \ No newline at end of file diff --git a/Carbon/Classes/Files.h b/Carbon/Classes/Files.h new file mode 100644 index 0000000..544e139 --- /dev/null +++ b/Carbon/Classes/Files.h @@ -0,0 +1,62 @@ +#include "ResKnife.h" + +#ifndef _ResKnife_Files_ +#define _ResKnife_Files_ + +/*! + @header File Handling Code + @discussion All code that reads from and writes to a file resides in here. Also contains open, save and revert dialog code. +*/ + +/*! + @function DisplayOpenDialog + @discussion Calls the appropriate Open() function for the system we are running on. +*/ +OSStatus DisplayOpenDialog( void ); +/*! + @function DisplayModelessGetFileDialog + @discussion Requires CarbonLib 1.1 or OS X +*/ +OSStatus DisplayModelessGetFileDialog( void ); +/*! + @function OpenFile +*/ +OSStatus OpenFile( short vRefNum, long dirID, ConstStr255Param fileName ); +/*! + @function DisplayStandardFileOpenDialog +*/ +OSStatus DisplayStandardFileOpenDialog( void ); +/*! + @function ModelessGetFileHandler + @discussion Requires CarbonLib 1.1 or OS X +*/ +pascal void ModelessGetFileHandler( const NavEventCallbackMessage callBackSelector, NavCBRecPtr cbRecord, NavCallBackUserData callBackUD ); +/*! + @function ModelessAskSaveChangesHandler + @discussion Requires CarbonLib 1.1 or OS X +*/ +pascal void ModelessAskSaveChangesHandler( const NavEventCallbackMessage callBackSelector, NavCBRecPtr cbRecord, NavCallBackUserData callBackUD ); +/*! + @function ModelessPutFileHandler + @discussion Requires CarbonLib 1.1 or OS X +*/ +pascal void ModelessPutFileHandler( const NavEventCallbackMessage callBackSelector, NavCBRecPtr cbRecord, NavCallBackUserData callBackUD ); +/*! + @function ModelessAskDiscardChangesHandler + @discussion Requires CarbonLib 1.1 or OS X +*/ +pascal void ModelessAskDiscardChangesHandler( const NavEventCallbackMessage callBackSelector, NavCBRecPtr cbRecord, NavCallBackUserData callBackUD ); +/*! + @function NavEventFilter +*/ +pascal void NavEventFilter( NavEventCallbackMessage callBackSelector, NavCBRecPtr cbRecord, NavCallBackUserData callBackUD ); +/*! + @function NavPreviewFilter +*/ +pascal Boolean NavPreviewFilter( NavCBRecPtr callBackParms, void *callBackUD ); +/*! + @function NavFileFilter +*/ +pascal Boolean NavFileFilter( AEDescPtr theItem, void *info, void *callBackUD, NavFilterModes filterMode ); + +#endif \ No newline at end of file diff --git a/Carbon/Classes/HostCallbacks.cpp b/Carbon/Classes/HostCallbacks.cpp new file mode 100644 index 0000000..79f1fc7 --- /dev/null +++ b/Carbon/Classes/HostCallbacks.cpp @@ -0,0 +1,186 @@ +#define _ResKnife_Plug_ 0 +#include "HostCallbacks.h" +#include "Application.h" +#include "WindowObject.h" +#include "EditorWindow.h" +#include "PlugObject.h" +#include "Errors.h" + +extern globals g; + +/* window management */ +Plug_WindowRef Host_RegisterWindow( Plug_PlugInRef plug, Plug_ResourceRef resource, WindowRef window ) +{ + if( resource ) + { + EditorWindowPtr plugWindow = new EditorWindow( ((ResourceObjectPtr) resource)->File(), (ResourceObjectPtr) resource, window ); + ((PlugObjectPtr) plug)->SetWindowObject( (WindowObjectPtr) plugWindow ); + ((PlugObjectPtr) plug)->SetResourceObject( (ResourceObjectPtr) resource ); + return (Plug_WindowRef) plugWindow; + } +/* else + { + PickerWindowPtr plugWindow = new PickerWindow( window ); + ((PlugObjectPtr) plug)->SetWindowObject( plugWindow ); + ((PlugObjectPtr) plug)->SetResourceObject( (ResourceObjectPtr) resource ); + return (Plug_WindowRef) plugWindow; + } +*/ else return null; +} + +#if !TARGET_API_MAC_CARBON + +void Host_InstallClassicWindowEventHandler( Plug_WindowRef plugWindow, RoutineDescriptor *handler ) +{ + ((PlugWindowPtr) plugWindow)->InstallClassicEventHandler( (ClassicEventHandlerProcPtr) handler ); +} + +#endif + +WindowRef Host_GetWindowRefFromPlugWindow( Plug_WindowRef plugWindow ) +{ + return ((WindowObjectPtr) plugWindow)->Window(); +} + +Plug_WindowRef Host_GetPlugWindowFromWindowRef( WindowRef window ) +{ + return (Plug_WindowRef) GetWindowRefCon(window); +} + +Plug_PlugInRef Host_GetPlugRef( WindowRef window ) +{ + #pragma unused( window ) + return null; +} + +Plug_ResourceRef Host_GetTargetResource( Plug_WindowRef plugWindow ) +{ + return (Plug_ResourceRef) ((EditorWindowPtr) plugWindow)->Resource(); +} + +/* accessors */ +Handle Host_GetResourceData( Plug_ResourceRef resource ) +{ + ((ResourceObjectPtr) resource)->Retain(); + return ((ResourceObjectPtr) resource)->Data(); +} + +Handle Host_GetPartialResourceData( Plug_ResourceRef resource, UInt32 offset, UInt32 length ) +{ + #pragma unused( resource, offset, length ) + return null; +} + +void Host_ReleaseResourceData( Plug_ResourceRef resource ) +{ + #pragma unused( resource ) +// ((ResourceObjectPtr) resource)->Release(); + return; +} + +void Host_ReleasePartialResourceData( Plug_ResourceRef resource, Handle data ) +{ + #pragma unused( resource, data ) + return; +} + +ResType Host_GetResourceType( Plug_ResourceRef resource ) +{ + return ((ResourceObjectPtr) resource)->Type(); +} + +SInt16 Host_GetResourceID( Plug_ResourceRef resource ) +{ + return ((ResourceObjectPtr) resource)->ID(); +} + +UInt32 Host_GetResourceSize( Plug_ResourceRef resource ) +{ + return ((ResourceObjectPtr) resource)->Size(); +} + +void Host_GetResourceName( Plug_ResourceRef resource, Str255 name ) +{ + #pragma unused( resource, name ) + return; +} + +UInt32 Host_GetWindowRefCon( Plug_WindowRef plugWindow ) +{ + return ((PlugWindowPtr) plugWindow)->GetRefCon(); +} + +void Host_SetWindowRefCon( Plug_WindowRef plugWindow, UInt32 value ) +{ + ((PlugWindowPtr) plugWindow)->SetRefCon(value); +} + +UInt32 Host_GetGlobalRefCon( Plug_PlugInRef plug ) +{ + return ((PlugObjectPtr) plug)->GetRefCon(); +} + +void Host_SetGlobalRefCon( Plug_PlugInRef plug, UInt32 value ) +{ + ((PlugObjectPtr) plug)->SetRefCon(value); +} + +Boolean Host_GetResourceDirty( Plug_ResourceRef resource ) +{ + return ((ResourceObjectPtr) resource)->Dirty(); +} + +void Host_SetResourceDirty( Plug_ResourceRef resource, Boolean dirty ) +{ + ((ResourceObjectPtr) resource)->SetDirty( dirty ); +} + +/* utilities */ +Handle Host_GetDefaultTemplate( ResType type ) +{ + short savedResFile = CurResFile(); + UseResFile( g.appResFile ); + Str255 name = "\pxxxx"; + BlockMoveData( &type, name +1, sizeof(ResType) ); + Handle tmpl = Get1NamedResource( 'TMPL', name ); + OSStatus error = ResError(); + UseResFile( savedResFile ); + if( error ) return null; + else return tmpl; +} + +void Host_AppendMenuToBar( Plug_PlugInRef plug, SInt16 resID ) +{ + #pragma unused( plug, resID ) + return; +} + +void Host_RemoveMenuFromBar( Plug_PlugInRef plug, SInt16 resID ) +{ + #pragma unused( plug, resID ) + return; +} + +void Host_UpdateMenus( Plug_ResourceRef resource ) +{ + OSStatus error = noErr; +#if TARGET_API_MAC_CARBON + error = CarbonEventUpdateMenus( null, null, null ); +// if( error ) DebugError( "\pHost_UpdateMenus hit an error when calling CarbonEventUpdateMenus()" ); + error = FileWindowUpdateMenus( null, null, ((ResourceObjectPtr) resource)->File() ); +// if( error ) DebugError( "\pHost_UpdateMenus hit an error when calling FileWindowUpdateMenus()" ); +#else + UpdateMenus( ((ResourceObjectPtr) resource)->File()->Window() ); +#endif +} + +void Host_DisplayError( ConstStr255Param errorStr, ConstStr255Param explanationStr, UInt8 severity ) +{ + #pragma unused( severity ) + DisplayError( errorStr, explanationStr ); +} + +void Host_DebugError( ConstStr255Param errorStr, OSStatus number ) +{ + DebugError( errorStr, number ); +} \ No newline at end of file diff --git a/Carbon/Classes/HostCallbacks.h b/Carbon/Classes/HostCallbacks.h new file mode 100644 index 0000000..3787e07 --- /dev/null +++ b/Carbon/Classes/HostCallbacks.h @@ -0,0 +1,245 @@ +#if !TARGET_API_MAC_OS8 + #include +#endif + +#ifndef _ResKnife_HostCallbacks_ +#define _ResKnife_HostCallbacks_ + +/*! + * @header Plug-in Import/Export + * @discussion The only file that both plug-ins and the host should include, this allows a one-to-one mapping between exported and imported functions. + */ + +/*! + * @typedef Plug_PlugInRef + * @abstract A global reference to your plug-in. + * @discussion When you plug-in is first loded it is assigned a unique reference number. This allows you to maintain a global refcon into which you can save a pointer to your globals. Thus any change to preferences can be maintained across all of your open editors, even if they are editing resources in different files, and is persistant across the entire time the host remains running. + */ +typedef struct OpaquePlugInObject* Plug_PlugInRef; +/*! + * @typedef Plug_WindowRef + * @abstract A reference to one of your editor or picker windows. + * @discussion The Plug_WindowRef allows a host to track your window and send it events + */ +typedef struct OpaqueWindowObject* Plug_WindowRef; +/*! + * @typedef Plug_ResourceRef + * @abstract A reference to one particular resource. + * @discussion Allows a plug to obtain information about any resource, not necessarily the one it is editing. For example, a 'TEXT' editor will probably want 'styl' resource data as well. + */ +typedef struct OpaqueResourceObject* Plug_ResourceRef; +/*! + * @typedef Plug_MenuCommand + * @abstract Passed to your menu parser when the user selects something. + * @discussion Contains the four-byte menu command defined in your xmnu resources or that which you chose when creating the menu in InterfaceBuilder. Only used with the Plug_HandleMenuCommand() function. + */ +typedef UInt32 Plug_MenuCommand; + +// idea taken from Starry Night (thanks Tom) +#if TARGET_API_MAC_OS8 +#if _ResKnife_Plug_ // you should #define this to be 1É + #define ResCall __declspec(dllimport) // functions exported by the host + #define ResCallBack __declspec(dllexport) // functions exported by the plug-in +#else // Éthe host #defines 0 + #define ResCall __declspec(dllexport) + #define ResCallBack __declspec(dllimport) +#endif +#else + #define ResCall + #define ResCallBack +#endif + +/*! + * @enum EditorType + * @discussion Allows a plug-in to tell the host what it does in a consice and simple way. + */ +typedef enum +{ + kPickerType = FOUR_CHAR_CODE('pick'), + kHexEditorType = FOUR_CHAR_CODE('hexa'), // returned ResTypes are ignored + kNormalEditorType = FOUR_CHAR_CODE('norm') // requires 'kind' to be set to the appropriate ResType +} EditorType; + +/*! + * @enum OpenMode + * @discussion Lets the plug-in influence how sub-editors are chosen when the plug requests that a new editor be opened + * via Host_OpenEditor(). If it cannot be fulfilled, a lesser type of window will open. + */ +typedef enum +{ + kOpenUsingEditor = 0, + kOpenUsingTemplate, + kOpenUsingHex +} OpenMode; + +/*! + * @enum Plug-in WindowKinds + * @discussion ID numbers for generic plug windows. + */ +enum +{ + kPlugWindowDocument = 1000, // your main window's windowkind is set to this by the host, do NOT change it. + kPlugWindowAboutBox, // windowkinds below 2000 are reserved + kPlugWindowPreferences +}; + +/*** IMPORTED FUNCTIONS ***/ +extern "C" // functions beginning "Host_" are in ResKnife/Resurrection +{ +/*! + * @function Host_RegisterWindow + * @abstract Registers plug-in windows with the host. + * @discussion Plug-ins should call this function immediatly after creating a window to swap their Mac OS WindowRef + * for a Plug_WindowRef, which allows the host to track the window, it's contents, and to send events there. + */ + ResCall Plug_WindowRef Host_RegisterWindow( Plug_PlugInRef plug, Plug_ResourceRef resource, WindowRef window ); +/*! + * @function Host_InstallClassicWindowEventHandler + * @abstract Without this, non-carbon plugs will not receive events + * @discussion After regestering you window, and only if you do not have access to the Carbon routine InstallWindowEventHandler(), + * your plug should call this to receive events. Events sent are currently limited to: + * + * • kEventWindowClickContentRgn + */ + ResCall void Host_InstallClassicWindowEventHandler( Plug_WindowRef plugWindow, RoutineDescriptor *handler ); +/*! + * @function Host_GetWindowRefFromPlugWindow + * @abstract Allows plug-ins to obtain a Mac OS WindowRef from their Plug_WindowRef + * @discussion This call must not be made before the window has been registered using Host_RegisterWindow() + */ + ResCall WindowRef Host_GetWindowRefFromPlugWindow( Plug_WindowRef plugWindow ); +/*! + * @function Host_GetPlugWindowFromWindowRef + * @abstract Allows plug-ins to obtain a Plug_WindowRef from their Mac OS WindowRef + * @discussion This call must not be made before the window has been registered using Host_RegisterWindow() + */ + ResCall Plug_WindowRef Host_GetPlugWindowFromWindowRef( WindowRef window ); +/*! + * @function Host_GetPlugRef + * @discussion You will probably need to call this if the system calls one of your routines directly. + */ + ResCall Plug_PlugInRef Host_GetPlugRef( WindowRef window ); +/*! + * @function Host_GetTargetResource + */ + ResCall Plug_ResourceRef Host_GetTargetResource( Plug_WindowRef plugWindow ); +/*! + * @function Host_GetResourceData + * @discussion Dispose of with Host_ReleaseResourceData() + */ + ResCall Handle Host_GetResourceData( Plug_ResourceRef resource ); +/*! + * @function Host_GetPartialResourceData + * @discussion Dispose of with Host_ReleasePartialResourceData() + */ + ResCall Handle Host_GetPartialResourceData( Plug_ResourceRef resource, UInt32 offset, UInt32 length ); +/*! + * @function Host_ReleaseResourceData + */ + ResCall void Host_ReleaseResourceData( Plug_ResourceRef resource ); +/*! + * @function Host_ReleasePartialResourceData + */ + ResCall void Host_ReleasePartialResourceData( Plug_ResourceRef resource, Handle data ); +/*! + * @function Host_GetResourceType + */ + ResCall ResType Host_GetResourceType( Plug_ResourceRef resource ); +/*! + * @function Host_GetResourceID + */ + ResCall SInt16 Host_GetResourceID( Plug_ResourceRef resource ); +/*! + * @function Host_GetResourceSize + */ + ResCall UInt32 Host_GetResourceSize( Plug_ResourceRef resource ); +/*! + * @function Host_GetResourceName + */ + ResCall void Host_GetResourceName( Plug_ResourceRef resource, Str255 name ); +/* ResCall void Host_SetResourceName( Plug_ResourceRef resource, ConstStr255Param name ); */ +/*! + * @function Host_GetResourceDirty + */ + ResCall Boolean Host_GetResourceDirty( Plug_ResourceRef resource ); +/*! + * @function Host_SetResourceDirty + */ + ResCall void Host_SetResourceDirty( Plug_ResourceRef resource, Boolean dirty ); +/* ResCall Boolean Host_GetResourceIsOnDisk( Plug_ResourceRef resource ); */ // name may change soon +/*! + * @function Host_GetWindowRefCon + */ + ResCall UInt32 Host_GetWindowRefCon( Plug_WindowRef plugWindow ); +/*! + * @function Host_SetWindowRefCon + */ + ResCall void Host_SetWindowRefCon( Plug_WindowRef plugWindow, UInt32 value ); +/*! + * @function Host_GetGlobalRefCon + */ + ResCall UInt32 Host_GetGlobalRefCon( Plug_PlugInRef plugRef ); +/*! + * @function Host_SetGlobalRefCon + */ + ResCall void Host_SetGlobalRefCon( Plug_PlugInRef plugRef, UInt32 value ); +/* ResCall OSStatus Host_OpenEditor( Plug_ResourceRef resource, ResOpenMode mode ); + ResCall OSStatus Host_SaveResource( Plug_ResourceRef resource ); // VERY IMPORTANT CALL! - Make when closing window + ResCall void Host_SetCursor( Plug_PlugInRef plugRef, Cursor *cursor ); + ResCall void Host_SetCursorToID( Plug_PlugInRef plugRef, SInt16 resID ); +*/ +/*! + * @function Host_GetDefaultTemplate + * @abstract Returns the default TMPL resource for the resource type passed in. + * @discussion Handle is NULL if no template exists. You must dispose of the handle yourself. + */ + ResCall Handle Host_GetDefaultTemplate( ResType type ); +/*! + * @function Host_AppendMenuToBar + * @discussion The host will track your window, and hide the menu when you are not fromtmost. + */ + ResCall void Host_AppendMenuToBar( Plug_PlugInRef plug, SInt16 resID ); +/*! + * @function Host_RemoveMenuFromBar + */ + ResCall void Host_RemoveMenuFromBar( Plug_PlugInRef plug, SInt16 resID ); +/*! + * @function Host_UpdateMenus + */ + ResCall void Host_UpdateMenus( Plug_ResourceRef resource ); +/*! + * @function Host_DisplayError + * @discussion Errors the user should see + */ + ResCall void Host_DisplayError( ConstStr255Param error, ConstStr255Param explanation, UInt8 severity ); +/*! + * @function Host_DebugError + * @discussion Errors the user shouldn't see + */ + ResCall void Host_DebugError( ConstStr255Param error, OSStatus number ); +} + +/*** EXPORTED FUNCTIONS ***/ +extern "C" // functions beginning "Plug_" should be in your plug-in editor +{ + /* required functions - plug-in won't be loaded if all these symbols cannot be found */ +/* ResCallBack OSStatus Plug_EditorType( Plug_PlugInRef plugRef, EditorType *type, ResType *kind, UInt8 *number ); // called to identify the number of different types of resources it can handle +*/ +/*! + * @function Plug_InitInstance + * @abstract Allows a plug-in to initalise itself and prepare to edit the resource. + * @discussion This is a required call. You must export this for your plug-in to be loaded. + * @param plug A reference which has been assigned to this plug-in. It will not necessarily remain constant, so do not save it beyond this call returning. + * @param resource A reference to the resource whose editing session has been requested. + */ + ResCallBack OSStatus Plug_InitInstance( Plug_PlugInRef plug, Plug_ResourceRef resource ); +/* ResCallBack OSStatus Plug_FlattenResource( Plug_PlugInRef plugRef, Plug_ResourceRef resource ); // update the handle provided by the host (see Host_GetResData) + ResCallBack OSStatus Plug_ResourceChanged( Plug_PlugInRef plugRef, Plug_ResourceRef resource ); // another editor has changed the resource your working on (normally responded to by calling Host_GetResData and a window update) +*/ /* optional functions - only called if they are requested & found */ +/* ResCallBack OSStatus Plug_UpdateMenu( Plug_PlugInRef plugRef, Plug_WindowRef windowObject ); + ResCallBack OSStatus Plug_HandleMenuCommand( Plug_PlugInRef plugRef, Plug_MenuCommand menuCmd, Boolean *handled ); + ResCallBack OSStatus Plug_HandleMenuItem( Plug_PlugInRef plugRef, SInt16 menuID, SInt16 itemID, Boolean *handled ); // name change + ResCallBack OSStatus Plug_AboutBox( Plug_PlugInRef plugRef ); +*/} + +#endif \ No newline at end of file diff --git a/Carbon/Classes/InspectorWindow.cpp b/Carbon/Classes/InspectorWindow.cpp new file mode 100644 index 0000000..70ef5a5 --- /dev/null +++ b/Carbon/Classes/InspectorWindow.cpp @@ -0,0 +1,265 @@ +#include "InspectorWindow.h" +#include "FileWindow.h" +#include "ResourceObject.h" +#include "Utility.h" +extern globals g; + + /*******************/ + /* WINDOW & EVENTS */ +/*******************/ + +/*** CONSTRUCTOR ***/ +InspectorWindow::InspectorWindow( void ) +{ + // if inspector already exists, return + if( g.inspector ) + { + SelectWindow( g.inspector->Window() ); + return; + } + +#if TARGET_API_MAC_CARBON + // create window + Str255 windowName; + Rect creationBounds; + SetRect( &creationBounds, 0, 0, kInspectorWindowWidth, kInspectorWindowHeight ); + OffsetRect( &creationBounds, 520, 45 ); + OSStatus error = CreateNewWindow( kFloatingWindowClass, kWindowStandardFloatingAttributes | kWindowStandardHandlerAttribute, &creationBounds, &window ); + GetIndString( windowName, kWindowNameStrings, kStringInspectorWindowName ); + SetWindowTitle( window, windowName ); + SetWindowKind( window, kInspectorWindowKind ); + SetThemeWindowBackground( window, kThemeBrushUtilityWindowBackgroundActive, false ); + + // install window event handler + EventHandlerRef ref = null; + EventHandlerUPP eventHandler = NewEventHandlerUPP( CloseInspectorWindow ); + EventTypeSpec events[] = { { kEventClassWindow, kEventWindowClose } }; + InstallWindowEventHandler( window, eventHandler, GetEventTypeCount(events), (EventTypeSpec *) &events, this, &ref ); + + // create root control + Rect bounds; + if( g.systemVersion < kMacOSX ) + { + ControlRef root; + CreateRootControl( window, &root ); + } + + // create image well + ControlRef imageWell; + ControlButtonContentInfo content; + content.contentType = kControlNoContent; + SetRect( &bounds, 0, 0, 44, 44 ); + OffsetRect( &bounds, 8, 8 ); + CreateImageWellControl( window, &bounds, &content, &imageWell ); + + // create static text controls + Rect windowRect; + ControlRef name, type, id; + ControlFontStyleRec fontStyle; + fontStyle.flags = kControlUseFontMask + kControlUseJustMask; + fontStyle.font = kControlFontSmallSystemFont; + fontStyle.just = teJustLeft; + GetWindowPortBounds( window, &windowRect ); + SetRect( &bounds, windowRect.left +60, windowRect.top +8, windowRect.right - windowRect.left -8, windowRect.top +36 ); + CreateStaticTextControl( window, &bounds, CFSTR(""), &fontStyle, &name ); + fontStyle.font = kControlFontSmallBoldSystemFont; + SetRect( &bounds, windowRect.left +60, windowRect.top +38, windowRect.right - windowRect.left -70, windowRect.top +52 ); + CreateStaticTextControl( window, &bounds, CFSTR(""), &fontStyle, &type ); + SetRect( &bounds, windowRect.right - windowRect.left -70, windowRect.top +38, windowRect.right - windowRect.left -8, windowRect.top +52 ); + CreateStaticTextControl( window, &bounds, CFSTR(""), &fontStyle, &id ); + + // create group control + ControlRef group; + GetWindowPortBounds( window, &bounds ); + InsetRect( &bounds, 8, 8 ); + bounds.top += kInspectorHeaderHeight; + CreateGroupBoxControl( window, &bounds, CFSTR("Attributes"), true, &group ); + + // create checkboxes + ControlRef changedBox, preloadBox, protectedBox, + lockedBox, purgeableBox, sysHeapBox; + InsetRect( &bounds, 4, 4 ); + bounds.top = bounds.bottom - kControlCheckBoxHeight; + CreateCheckBoxControl( window, &bounds, CFSTR("System Heap"), kControlCheckBoxUncheckedValue, true, &sysHeapBox ); + bounds.top -= kControlCheckBoxHeight; + bounds.bottom -= kControlCheckBoxHeight; + CreateCheckBoxControl( window, &bounds, CFSTR("Purgeable"), kControlCheckBoxUncheckedValue, true, &purgeableBox ); + bounds.top -= kControlCheckBoxHeight; + bounds.bottom -= kControlCheckBoxHeight; + CreateCheckBoxControl( window, &bounds, CFSTR("Locked"), kControlCheckBoxUncheckedValue, true, &lockedBox ); + bounds.top -= kControlCheckBoxHeight; + bounds.bottom -= kControlCheckBoxHeight; + CreateCheckBoxControl( window, &bounds, CFSTR("Protected"), kControlCheckBoxUncheckedValue, true, &protectedBox ); + bounds.top -= kControlCheckBoxHeight; + bounds.bottom -= kControlCheckBoxHeight; + CreateCheckBoxControl( window, &bounds, CFSTR("Preload"), kControlCheckBoxUncheckedValue, true, &preloadBox ); + bounds.top -= kControlCheckBoxHeight; + bounds.bottom -= kControlCheckBoxHeight; + CreateCheckBoxControl( window, &bounds, CFSTR("Changed"), kControlCheckBoxUncheckedValue, true, &changedBox ); + + // embed controls + EmbedControl( changedBox, group ); + EmbedControl( preloadBox, group ); + EmbedControl( protectedBox, group ); + EmbedControl( lockedBox, group ); + EmbedControl( purgeableBox, group ); + EmbedControl( sysHeapBox, group ); +#else + if( g.useAppearance && g.systemVersion >= kMacOSEight ) + window = GetNewCWindow( kFileWindow8, null, kFirstWindowOfClass ); + else + window = GetNewCWindow( kFileWindow7, null, kFirstWindowOfClass ); +#endif + + // update and show window + Update(); + ShowWindow( window ); + g.inspector = this; +} + +/*** DESTRUCTOR ***/ +InspectorWindow::~InspectorWindow( void ) +{ + g.inspector = null; +} + +/*** CLOSW WINDOW EVENT HANDLER ***/ +pascal OSStatus CloseInspectorWindow( EventHandlerCallRef callRef, EventRef event, void *userData ) +{ + #pragma unused( callRef, event, userData ) + if( g.inspector ) delete g.inspector; + return eventNotHandledErr; +} + +/*** UPDATE WINDOW ***/ +OSStatus InspectorWindow::Update( RgnHandle region ) +{ + #pragma unused( region ) +#if TARGET_API_MAC_CARBON + // get target file + FileWindowPtr file = null; + WindowRef fileWindow = GetFrontWindowOfClass( kDocumentWindowClass, true ); + if( !fileWindow ) return noErr; // no window is open - BUG: items in window are not cleared + + OSStatus error = noErr; + Boolean validWindow = false; + while( !validWindow || error ) + { + WindowKind kind = (WindowKind) GetWindowKind( fileWindow ); + if( kind != kFileWindowKind ) + { + fileWindow = GetNextWindowOfClass( fileWindow, kDocumentWindowClass, true ); + if( !window ) error = paramErr; + } + else + { + file = (FileWindowPtr) GetWindowRefCon( fileWindow ); + if( file ) validWindow = true; + else error = paramErr; + } + } + if( error ) return error; + + // get selection + UInt32 itemCount; + ControlRef browser = null; + GetWindowProperty( fileWindow, kResKnifeCreator, kDataBrowserSignature, sizeof(ControlRef), null, &browser ); + GetDataBrowserItemCount( browser, kDataBrowserNoItem, true, kDataBrowserItemIsSelected, &itemCount ); + + // get controls + ControlRef root, well, name, type, id, group; + ControlRef changedBox, preloadBox, protectedBox, lockedBox, purgeableBox, sysHeapBox; + ControlButtonContentInfo content; + GetRootControl( window, &root ); + GetIndexedSubControl( root, 1, &well ); + GetIndexedSubControl( root, 2, &name ); + GetIndexedSubControl( root, 3, &type ); + GetIndexedSubControl( root, 4, &id ); + GetIndexedSubControl( root, 5, &group ); + GetIndexedSubControl( group, 1, &changedBox ); + GetIndexedSubControl( group, 2, &preloadBox ); + GetIndexedSubControl( group, 3, &protectedBox ); + GetIndexedSubControl( group, 4, &lockedBox ); + GetIndexedSubControl( group, 5, &purgeableBox ); + GetIndexedSubControl( group, 6, &sysHeapBox ); + + if( itemCount != 1 ) + { + // set icon + content.contentType = kControlNoContent; + SetImageWellContentInfo( well, &content ); + DrawOneControl( well ); // bug: work around for bug in ControlManager +// DisableControl( well ); + + // set name + StringPtr blank = (StringPtr) NewPtrClear( sizeof(Str255) ); + CopyPascalStringToC( "\p", (char *) blank ); + SetControlData( name, kControlLabelPart, kControlStaticTextTextTag, 1, blank ); + SetControlTitle( name, "\p" ); + + // set type + SetControlData( type, kControlLabelPart, kControlStaticTextTextTag, 1, blank ); + SetControlTitle( type, "\p" ); + + // set ID + SetControlData( id, kControlLabelPart, kControlStaticTextTextTag, 1, blank ); + SetControlTitle( id, "\p" ); + + // set control values + SetControlValue( changedBox, kControlCheckBoxUncheckedValue ); + SetControlValue( preloadBox, kControlCheckBoxUncheckedValue ); + SetControlValue( protectedBox, kControlCheckBoxUncheckedValue ); + SetControlValue( lockedBox, kControlCheckBoxUncheckedValue ); + SetControlValue( purgeableBox, kControlCheckBoxUncheckedValue ); + SetControlValue( sysHeapBox, kControlCheckBoxUncheckedValue ); +// DisableControl( group ); + } + else + { + // get selected resource + DataBrowserItemID first, last; + GetDataBrowserSelectionAnchor( browser, &first, &last ); // first must == last + ResourceObjectPtr resource = file->GetResource(first); + + // set icon + content.contentType = kControlContentIconSuiteRes; + content.u.resID = kDefaultResourceIcon; + SetImageWellContentInfo( well, &content ); + DrawOneControl( well ); // bug: work around for bug in ControlManager +// EnableControl( well ); + + // set name + StringPtr label = (StringPtr) NewPtrClear( sizeof(Str255) ); + if( PStringLength( resource->Name()) == 0 ) GetIndString( label, kResourceNameStrings, kStringUntitledResource ); + else CopyPascalStringToC( resource->Name(), (char *) label ); + SetControlData( name, kControlLabelPart, kControlStaticTextTextTag, PStringLength(resource->Name()), label ); + SetControlTitle( name, resource->Name() ); + + // set type + Str255 string; + TypeToPString( resource->Type(), string ); + CopyPascalStringToC( string, (char *) label ); + SetControlData( type, kControlLabelPart, kControlStaticTextTextTag, string[0], label ); + SetControlTitle( type, string ); + + // set ID + NumToString( resource->ID(), string ); + CopyPascalStringToC( string, (char *) label ); + SetControlData( id, kControlLabelPart, kControlStaticTextTextTag, string[0], label ); + SetControlTitle( id, string ); + + // set control values + SetControlValue( changedBox, (resource->Attributes() & resChanged)? kControlCheckBoxCheckedValue : kControlCheckBoxUncheckedValue ); + SetControlValue( preloadBox, (resource->Attributes() & resPreload)? kControlCheckBoxCheckedValue : kControlCheckBoxUncheckedValue ); + SetControlValue( protectedBox, (resource->Attributes() & resProtected)? kControlCheckBoxCheckedValue : kControlCheckBoxUncheckedValue ); + SetControlValue( lockedBox, (resource->Attributes() & resLocked)? kControlCheckBoxCheckedValue : kControlCheckBoxUncheckedValue ); + SetControlValue( purgeableBox, (resource->Attributes() & resPurgeable)? kControlCheckBoxCheckedValue : kControlCheckBoxUncheckedValue ); + SetControlValue( sysHeapBox, (resource->Attributes() & resSysHeap)? kControlCheckBoxCheckedValue : kControlCheckBoxUncheckedValue ); + DeactivateControl( changedBox ); +// EnableControl( group ); + } + return error; +#else + return noErr; +#endif +} \ No newline at end of file diff --git a/Carbon/Classes/InspectorWindow.h b/Carbon/Classes/InspectorWindow.h new file mode 100644 index 0000000..798cab5 --- /dev/null +++ b/Carbon/Classes/InspectorWindow.h @@ -0,0 +1,26 @@ +#include "ResKnife.h" +#include "WindowObject.h" + +/*! + @header InspectorWindow + @discussion Manages the little inspector that accompanies a File Window. +*/ + +/* INSPECTOR WINDOW CLASS */ +class InspectorWindow : WindowObject +{ +public: + InspectorWindow( void ); + ~InspectorWindow( void ); + virtual OSStatus Update( RgnHandle region = null ); // unused parameter +}; + +pascal OSStatus CloseInspectorWindow( EventHandlerCallRef callRef, EventRef event, void *userData ); + +// inspector window dimentions +const UInt16 kInspectorHeaderHeight = 48 + 4; // 48 for huge icon +const UInt16 kInspectorWindowWidth = 183; +const UInt16 kInspectorWindowHeight = 183; + +// non-window dimentions +const UInt16 kControlCheckBoxHeight = 16; diff --git a/Carbon/Classes/PickerWindow.cpp b/Carbon/Classes/PickerWindow.cpp new file mode 100644 index 0000000..dde1d34 --- /dev/null +++ b/Carbon/Classes/PickerWindow.cpp @@ -0,0 +1,73 @@ +#include "PickerWindow.h" +#include "FileWindow.h" +#include "Errors.h" +#include "Utility.h" + +extern globals g; + +/*** CREATOR ***/ +PickerWindow::PickerWindow( FileWindowPtr ownerFile, ResType resType ) : PlugWindow( ownerFile ) +{ + OSStatus error = noErr; + +#if USE_NIBS + // create a nib reference (only searches the application bundle) + IBNibRef nibRef = null; + error = CreateNibReference( CFSTR("ResKnife"), &nibRef ); + if( error != noErr || nibRef == null ) + { + DisplayError( "\pThe nib file reference could not be obtained." ); + return; + } + + // create window + error = CreateWindowFromNib( nibRef, CFSTR("Picker Window"), &window ); + if( error != noErr || window == null ) + { + DisplayError( "\pA picker window could not be obtained from the nib file." ); + return; + } + + // dispose of nib ref + DisposeNibReference( nibRef ); + +#elif TARGET_API_MAC_CARBON + // create window + Rect creationBounds; + SetRect( &creationBounds, 9, 45, 256 +9, 256 +45 ); + error = CreateNewWindow( kDocumentWindowClass, kWindowStandardDocumentAttributes | kWindowStandardHandlerAttribute, &creationBounds, &window ); +#else + if( g.useAppearance && g.systemVersion >= kMacOSEight ) + { + window = GetNewCWindow( kFileWindow8, null, kFirstWindowOfClass ); + themeSavvy = true; + } + else + { + window = GetNewCWindow( kFileWindow7, null, kFirstWindowOfClass ); + themeSavvy = false; + } +#endif + + // set up default window title + Str255 windowTitle, resTypeStr; + FSSpec spec = *ownerFile->GetFileSpec(); + CopyPString( spec.name, windowTitle ); + TypeToPString( resType, resTypeStr ); + AppendPString( windowTitle, "\p: " ); + AppendPString( windowTitle, resTypeStr ); + AppendPString( windowTitle, "\p resources" ); + + // save PickerWindow class in window's refcon + SetWindowRefCon( window, (UInt32) this ); + SetWindowKind( window, kPickerWindowKind ); + SetWindowTitle( window, windowTitle ); + + // set window's background to default for theme +#if TARGET_API_MAC_CARBON + SetThemeWindowBackground( window, kThemeBrushDocumentWindowBackground, true ); +#else + if( g.useAppearance ) + SetThemeWindowBackground( window, kThemeBrushDocumentWindowBackground, false ); +#endif +} \ No newline at end of file diff --git a/Carbon/Classes/PickerWindow.h b/Carbon/Classes/PickerWindow.h new file mode 100644 index 0000000..5ad89d4 --- /dev/null +++ b/Carbon/Classes/PickerWindow.h @@ -0,0 +1,23 @@ +#include "ResKnife.h" +#include "PlugWindow.h" + +#ifndef _ResKnife_PickerWindow_ +#define _ResKnife_PickerWindow_ + +/*! + @header PickerWindow + @discussion A class specifically designed to maintain an external picker window. +*/ + +/*! + @class PickerWindow + @discussion A class specifically designed to maintain an external picker window. +*/ +class PickerWindow : PlugWindow +{ +public: +// methods + PickerWindow( FileWindowPtr ownerFile, ResType resType ); +}; + +#endif \ No newline at end of file diff --git a/Carbon/Classes/PlugObject.cpp b/Carbon/Classes/PlugObject.cpp new file mode 100644 index 0000000..cc6b3f9 --- /dev/null +++ b/Carbon/Classes/PlugObject.cpp @@ -0,0 +1,95 @@ +#include "PlugObject.h" +#include "Errors.h" +extern globals g; + +/*** LOAD EDITOR ***/ +OSStatus LoadEditor( ResourceObjectPtr resource, ConstStr63Param libName ) +{ + // create editor window structure and save resource to be edited into it + OSStatus error = noErr; + PlugObjectPtr plug = (PlugObjectPtr) NewPtrClear( sizeof(PlugObject) ); // bug: this function calls itself, so memory leak may exist here + if( !plug ) return memFullErr; // another bug: should check if plug already has associated PlugObject (ie it is loaded already) - globalRefCon won't work + + + // load editor's library into memory + CFragConnectionID connID = null; + Ptr mainAddr = null; + Str255 errMessage; + error = GetSharedLibrary( libName, kPowerPCCFragArch, kLoadCFrag, &connID, &mainAddr, errMessage ); + DebugError( errMessage ); + if( error ) + { + if( EqualString( libName, "\pHex Editor", true, true ) ) + { + DisposePtr( (Ptr) plug ); // only dispose if was created just now + DisplayError( "\pNo editors are available for this resource!", "\pI suggest you reinstall the program." ); + return error; + } + else if( EqualString( libName, "\pTemplate Editor", true, true ) ) + { + error = LoadEditor( resource, "\pHex Editor" ); + return error; + } + else + { + error = LoadEditor( resource, "\pTemplate Editor" ); + if( error ) + error = LoadEditor( resource, "\pHex Editor" ); + return error; + } + } + plug->SetConnectionID( connID ); + + // find and call InitInstance symbol + InitPlugProcPtr symAddr; + CFragSymbolClass symClass; + error = FindSymbol( connID, "\pPlug_InitInstance", (Ptr *) &symAddr, &symClass ); + if( error ) + { + DisposePtr( (Ptr) plug ); // only dispose if was created just now + if( g.debug ) DebugError( "\pPlug_InitInstance() could not be found." ); + else DisplayError( "\pCannot load up requested editor.", "\pPlease obtain an update from the plug-in's author." ); + return error; + } + error = (* symAddr)( plug, resource ); + if( error ) + { + DisposePtr( (Ptr) plug ); // only dispose if was created just now + DebugError( "\pPlug_InitInstance() returned an error. This is quite normal for the Template Editor, thus if no XXXX Editor exists, and no template is found, you will see this dialog, then a hex editor appear." ); + } + return error; +} + +/*** UNLOAD EDITOR ***/ +OSStatus UnloadEditor( PlugObjectPtr plug ) +{ + #pragma unused( plug ) + OSStatus error = noErr; + CFragConnectionID connID = plug->GetConnectionID(); + + // find and call DisposeEditor symbol +/* DisposePlugProcPtr symAddr; + CFragSymbolClass symClass; + error = FindSymbol( connID, "\pPlug_DisposeEditor", (Ptr *) &symAddr, &symClass ); + if( error ) return error; + (* symAddr)( plug ); +*/ + // close connection to editor + error = CloseConnection( &connID ); + plug->SetConnectionID( null ); + return error; +} + + /**************************/ + /* CLASS ACCESSOR METHODS */ +/**************************/ + +/*** ACCESSORS ***/ +void PlugObject::SetRefCon( UInt32 value ) { refcon = value; } +UInt32 PlugObject::GetRefCon( void ) { return refcon; } +CFragConnectionID PlugObject::GetConnectionID( void ) { return connID; } +void PlugObject::SetConnectionID( CFragConnectionID newID ) { connID = newID; } +WindowObjectPtr PlugObject::GetWindowObject( void ) { return windowObj; } +void PlugObject::SetWindowObject( WindowObjectPtr newWindowObj ) { windowObj = newWindowObj; } +ResourceObjectPtr PlugObject::GetResourceObject( void ) { return resourceObj; } +void PlugObject::SetResourceObject( ResourceObjectPtr newResourceObj ) { resourceObj = newResourceObj; } \ No newline at end of file diff --git a/Carbon/Classes/PlugObject.h b/Carbon/Classes/PlugObject.h new file mode 100644 index 0000000..fbb709e --- /dev/null +++ b/Carbon/Classes/PlugObject.h @@ -0,0 +1,97 @@ +#include "ResKnife.h" +#include "WindowObject.h" +#include "ResourceObject.h" + +#ifndef _ResKnife_PlugObject_ +#define _ResKnife_PlugObject_ + +/*! + @header PlugObject + @discussion Keeps track of all loaded plug-ins. +*/ + +/*! + @class PlugObject + @abstract Contains the data required to know which plug-ins can edit what. + @discussion Created when a plug-in is loaded for the first time, and is never destoyed, this class maintains the connection between the host and a plug-in. Also tracks whether the plug is an Editor or a Picker. +*/ +typedef class PlugObject +{ +/*! @var connID A CFM connection ID to a plug-in. */ + CFragConnectionID connID; +/*! @var windowObj The first window loaded for this plug-in. Either a PickerWindow or an EditorWindow. */ + WindowObjectPtr windowObj; +/*! @var resourceObj What the first loaded window edits (if anything). */ + ResourceObjectPtr resourceObj; +// EditorType editorType; +/*! @var ResType The type of resource this plug can handle. */ + ResType whatIActuallyEdit; + + // for the plug's use +/*! @var refcon A plug-global refcon for storing (say) it's preferences structure. */ + UInt32 refcon; + +public: +/*! + @function GetConnectionID + @discussion Accessor function. +*/ + CFragConnectionID GetConnectionID( void ); +/*! + @function SetConnectionID + @discussion Accessor function. +*/ + void SetConnectionID( CFragConnectionID newID ); +/*! + @function GetWindowObject + @discussion Accessor function. +*/ + WindowObjectPtr GetWindowObject( void ); +/*! + @function SetWindowObject + @discussion Accessor function. +*/ + void SetWindowObject( WindowObjectPtr newWindowObj ); +/*! + @function GetResourceObject + @discussion Accessor function. +*/ + ResourceObjectPtr GetResourceObject( void ); +/*! + @function SetResourceObject + @discussion Accessor function. +*/ + void SetResourceObject( ResourceObjectPtr newResourceObj ); +/*! + @function SetRefCon + @discussion Accessor function. +*/ + void SetRefCon( UInt32 value ); +/*! + @function GetRefCon + @discussion Accessor function. +*/ + UInt32 GetRefCon( void ); +} PlugObject, *PlugObjectPtr; + +/*! + @function LoadEditor + @discussion Loads a CFrag by name, and passes it a resource to edit. + @param resource The resource to be edited. + @param libName A string containing the fragment name of the editor to be used, for example "icns Editor" or "PICT Picker". +*/ +OSStatus LoadEditor( ResourceObjectPtr resource, ConstStr63Param libName ); +/*! + @function UnloadEditor + @discussion Unloads the given plug. + @param plug The plug-in to be killed. +*/ +OSStatus UnloadEditor( PlugObjectPtr plug ); + +/*! + @typedef InitPlugProcPtr + @discussion The pointer to Plug_InitInstance() that FindSymbol returns. +*/ +typedef OSStatus (* InitPlugProcPtr)( PlugObjectPtr plug, ResourceObjectPtr resource ); + +#endif \ No newline at end of file diff --git a/Carbon/Classes/PlugWindow.cpp b/Carbon/Classes/PlugWindow.cpp new file mode 100644 index 0000000..0fc4b55 --- /dev/null +++ b/Carbon/Classes/PlugWindow.cpp @@ -0,0 +1,91 @@ +#include "PlugWindow.h" +#include "Utility.h" +#include "string.h" +extern globals g; + + /*********************/ + /* PLUG WINDOW CLASS */ +/*********************/ + +/*** CREATOR ***/ +PlugWindow::PlugWindow( FileWindowPtr ownerFile ) +{ + memset( this, 0, sizeof(PlugWindow) ); + file = ownerFile; +} + +/*** GET FILE WINDOW ***/ +FileWindowPtr PlugWindow::File( void ) +{ + return file; +} + +#if !TARGET_API_MAC_CARBON + +/*** INSTALL CLASSIC EVENT HANDLER ***/ +void PlugWindow::InstallClassicEventHandler( ClassicEventHandlerProcPtr newHandler ) +{ + handler = newHandler; +} + +/*** UPDATE WINDOW ***/ +OSStatus PlugWindow::Update( RgnHandle region ) +{ + EventRecord event; + event.what = updateEvt; + event.message = (UInt32) window; + event.when = (UInt32) region; // note overload here + event.where = NewPoint(); + event.modifiers = null; + + OSStatus error = (* handler)( &event, kEventWindowUpdate, null ); + return error; +} + +/*** ACTIVATE WINDOW ***/ +OSStatus PlugWindow::Activate( Boolean active ) +{ + EventRecord event; + event.what = activateEvt; + event.message = (UInt32) window; + event.when = TickCount(); + event.where = NewPoint(); + event.modifiers = null; + + OSStatus error = (* handler)( &event, active? kEventWindowActivated:kEventWindowDeactivated, null ); + return error; +} + +/*** CLOSE WINDOW ***/ +OSStatus PlugWindow::Close( void ) +{ + EventRecord event; + event.what = mouseUp; + event.message = (UInt32) window; + event.when = TickCount(); + event.where = NewPoint(); + event.modifiers = null; + + OSStatus error = (* handler)( &event, kEventWindowClose, null ); + return error; +} + +/*** HANDLE CLICK IN WINDOW ***/ +OSStatus PlugWindow::Click( Point mouse, EventModifiers modifiers ) +{ + EventRecord event; + event.what = mouseDown; + event.message = (UInt32) window; + event.when = TickCount(); + event.where = mouse; + event.modifiers = modifiers; + + OSStatus error = (* handler)( &event, kEventWindowClickContentRgn, null ); + return error; +} + +#endif + +/*** ACCESSORS ***/ +void PlugWindow::SetRefCon( UInt32 value ) { refcon = value; } +UInt32 PlugWindow::GetRefCon( void ) { return refcon; } diff --git a/Carbon/Classes/PlugWindow.h b/Carbon/Classes/PlugWindow.h new file mode 100644 index 0000000..fb464df --- /dev/null +++ b/Carbon/Classes/PlugWindow.h @@ -0,0 +1,78 @@ +#include "ResKnife.h" +#include "WindowObject.h" + +#ifndef _ResKnife_PlugWindow_ +#define _ResKnife_PlugWindow_ + +/*! + * @header PlugWindow + * @discussion Declares the (very simple) PlugWindow subclass. + */ + +#if !TARGET_API_MAC_CARBON + +// classic event handler +typedef CALLBACK_API(OSStatus, ClassicEventHandlerProcPtr) (EventRecord *event, UInt32 eventKind, void *userData); +typedef STACK_UPP_TYPE(ClassicEventHandlerProcPtr) ClassicEventHandlerUPP; +enum { uppClassicEventHandlerProcInfo = 0x00000FF0 }; /* pascal 4_bytes Func(4_bytes, 4_bytes, 4_bytes) */ +#ifdef __cplusplus + inline ClassicEventHandlerUPP NewClassicEventHandlerUPP(ClassicEventHandlerProcPtr userRoutine) { return (ClassicEventHandlerUPP)NewRoutineDescriptor((ProcPtr)(userRoutine), uppClassicEventHandlerProcInfo, GetCurrentArchitecture()); } +#else + #define NewClassicEventHandlerUPP(userRoutine) (ClassicEventHandlerUPP)NewRoutineDescriptor((ProcPtr)(userRoutine), uppClassicEventHandlerProcInfo, GetCurrentArchitecture()) +#endif + +#endif + +/*! + * @class PlugWindow + * @abstract Base class for EditorWindow and PickerWindow. + * @discussion Declares the classic event handler & a refcon, and overrides a few classic events. + */ +class PlugWindow : public WindowObject +{ +protected: +/*! @var file The owning file of the window. */ + FileWindowPtr file; +#if !TARGET_API_MAC_CARBON +/*! @var handler The classic event handler used for plug windows. */ + ClassicEventHandlerProcPtr handler; +#endif +/*! @var refcon A refcon for the plug to use. */ + UInt32 refcon; + +public: +/*! + * @function PlugWindow + */ + PlugWindow( FileWindowPtr ownerFile ); +/*! + * @function File + */ + FileWindowPtr File( void ); +#if !TARGET_API_MAC_CARBON +/*! + * @function InstallClassicEventHandler + */ + void InstallClassicEventHandler( ClassicEventHandlerProcPtr newHandler ); +/*! + * @function Close + */ + virtual OSStatus Close( void ); +/*! + * @function Activate + */ + virtual OSStatus Activate( Boolean active = true ); +/*! + * @function Update + */ + virtual OSStatus Update( RgnHandle region = null ); +/*! + * @function Click + */ + virtual OSStatus Click( Point mouse, EventModifiers modifiers ); +#endif + void SetRefCon( UInt32 value ); + UInt32 GetRefCon( void ); +}; + +#endif diff --git a/Carbon/Classes/ResourceObject.cpp b/Carbon/Classes/ResourceObject.cpp new file mode 100644 index 0000000..29e381b --- /dev/null +++ b/Carbon/Classes/ResourceObject.cpp @@ -0,0 +1,100 @@ +#include "ResourceObject.h" +#include "Errors.h" +#include "string.h" + +/*** CREATOR ***/ +ResourceObject::ResourceObject( FileWindowPtr owner ) +{ + // set contents to zero + memset( this, 0, sizeof(ResourceObject) ); + + file = owner; + nameIconRgn = NewRgn(); +} + +/*** DESTRUCTOR ***/ +ResourceObject::~ResourceObject( void ) +{ + if( nameIconRgn ) DisposeRgn( nameIconRgn ); + if( data ) DisposeHandle( data ); +} + +/*** RETAIN ***/ +OSStatus ResourceObject::Retain( void ) +{ + OSStatus error = noErr; +/* if( retainCount == 0 ) + { + if( dataFork ) + { + // open file for reading + SInt16 refNum; + error = FSpOpenDF( file->GetFileSpec(), fsRdPerm, &refNum ); + if( error ) + { + DisplayError( "\pData fork could not be read", "\pThis file appears to be corrupted. Although the resources could be read in correctly, the data fork could not be found. Please run Disk First Aid to correct the problem." ); + return error; + } + + // get new handle + data = NewHandleClear( size ); + if( !data || MemError() ) + { + DisplayError( "\pNot enough memory to read data fork", "\pPlease quit other applications and try again." ); + FSClose( refNum ); + return memFullErr; + } + + // read data fork + HLock( data ); + error = FSRead( refNum, (long *) &size, *data ); + HUnlock( data ); + FSClose( refNum ); + } + else + { + LoadResource( data ); + } + } +*/ retainCount++; + return error; +} + +/*** RELEASE ***/ +void ResourceObject::Release( void ) +{ + if( retainCount > 0 ) + { +/* if( retainCount == 1 ) + DisposeHandle( data ); +*/ retainCount--; + } +} + +/*** SET RESOURCE DIRTY ***/ +void ResourceObject::SetDirty( Boolean value ) +{ + dirty = value; + file->SetFileDirty( value ); + + // being here indicates the resource size parameter also needs updating + size = GetHandleSize( data ); + + // bug: should now tell all open copies of this resource to update themselves +} + +/*** RES INFO RECORD ACCESSORS ***/ +FileWindowPtr ResourceObject::File( void ) { return file; } +ResourceObjectPtr ResourceObject::Next( void ) { return next; } +Boolean ResourceObject::Dirty( void ) { return dirty; } +void ResourceObject::Select( Boolean select ) { selected = select; } +Boolean ResourceObject::Selected( void ) { return selected; } +DataBrowserItemID ResourceObject::Number( void ) { return number; } +Boolean ResourceObject::RepresentsDataFork( void ) { return dataFork; } + +Handle ResourceObject::Data( void ) { return data; } +UInt8* ResourceObject::Name( void ) { return name; } +UInt32 ResourceObject::Size( void ) { return size; } +ResType ResourceObject::Type( void ) { return type; } +SInt16 ResourceObject::ID( void ) { return resID; } +SInt16 ResourceObject::Attributes( void ) { return attribs; } \ No newline at end of file diff --git a/Carbon/Classes/ResourceObject.h b/Carbon/Classes/ResourceObject.h new file mode 100644 index 0000000..9dcea9f --- /dev/null +++ b/Carbon/Classes/ResourceObject.h @@ -0,0 +1,159 @@ +#include "ResKnife.h" +#include "FileWindow.h" // for friends + +#ifndef _ResKnife_ResourceObject_ +#define _ResKnife_ResourceObject_ + +/*! + @header ResourceObject + @discussion Contains all required information for a resource, including it's parent file and whether or not it has been edited since it was last saved (if ever). +*/ + +/*! + @class ResourceObject + @abstract Opaque + @discussion Mainly consisting of regular controls such as scroll bars and headers, this doesn't do much by it's self. +*/ +class ResourceObject +{ +/*! @var file The parent file into which this resource is to be saved. */ + FileWindowPtr file; +/*! @var next the next resource. */ + ResourceObjectPtr next; + + // status of resource +// Boolean neverSaved; // resource has never been saved -- cleared on SaveFile() +/*! @var dirty Resource has been modified since the file was last saved if this flag is true. Cleared when SaveFile() is called. */ + UInt16 retainCount; // counts the number of times this resource has been loaded minus the number it's been released + Boolean dirty; +// Boolean update; // resource is not synched with temporary file -- cleared on UpdateFile() + Boolean dataFork; // resource represents data fork, only true in files whose resource map is in the resource fork +/*! @var number Item number in the data browser. Deleted resources vacate their number, and it is not replaced if a new resource is created. New resources are appended to the end of the chain, and will always have the highest numbers. */ + DataBrowserItemID number; + + // classic display parameters +/*! @var nameIconRgn */ + RgnHandle nameIconRgn; +/*! @var selected */ + Boolean selected; + + // resource +/*! @var type */ + ResType type; +/*! @var size */ + SInt32 size; +/*! @var resID */ + SInt16 resID; +/*! @var attribs */ + SInt16 attribs; +/*! @var data The actual resource byte stream. */ + Handle data; +/*! @var name */ + Str255 name; + + /* methods */ +public: +/*! + @function ResourceObject + @discussion Creator function. +*/ + ResourceObject( FileWindowPtr owner = null ); +/*! + @function ~ResourceObject + @discussion Destructor function. +*/ + ~ResourceObject( void ); +/*! + @function Retain + @discussion Accessor function. +*/ + OSStatus Retain( void ); +/*! + @function Release + @discussion Accessor function. +*/ + void Release( void ); +/*! + @function File + @discussion Accessor function. +*/ + FileWindowPtr File( void ); +/*! + @function Next + @discussion Accessor function. +*/ + ResourceObjectPtr Next( void ); +/*! + @function SetDirty + @discussion Accessor function. +*/ + void SetDirty( Boolean value ); +/*! + @function Dirty + @discussion Accessor function. +*/ + Boolean Dirty( void ); +/*! + @function Select + @discussion Accessor function. + @param select Pass true to select, false to deselect this resource in the file window. +*/ + void Select( Boolean select ); +/*! + @function Selected + @discussion Accessor function. +*/ + Boolean Selected( void ); +/*! + @function Number + @discussion Accessor function. +*/ + DataBrowserItemID Number( void ); +/*! + @function RepresentsDataFork + @discussion Accessor function. +*/ + Boolean RepresentsDataFork( void ); +/*! + @function Data + @discussion Accessor function. Warning: This functions returns the ACTUAL data handle - do not dispose of it. +*/ + Handle Data( void ); +/*! + @function Name + @discussion Accessor function. +*/ + UInt8* Name( void ); +/*! + @function Size + @discussion Accessor function. +*/ + UInt32 Size( void ); +/*! + @function Type + @discussion Accessor function. +*/ + ResType Type( void ); +/*! + @function ID + @discussion Accessor function. +*/ + SInt16 ID( void ); +/*! + @function Attributes + @discussion Accessor function. +*/ + SInt16 Attributes( void ); + + friend OSStatus FileWindow::ReadResourceMap( void ); + friend OSStatus FileWindow::ReadDataFork( OSStatus RFError ); + friend OSStatus FileWindow::InitDataBrowser( void ); +#if !TARGET_API_MAC_CARBON + friend OSStatus FileWindow::Click( Point mouse, EventModifiers modifiers ); + friend OSStatus FileWindow::DrawResourceIcon( ResourceObjectPtr resource, UInt16 line ); +#endif + friend OSStatus FileWindow::CreateNewResource( ConstStr255Param name, ResType type, SInt16 resID, SInt16 attribs ); + friend OSStatus FileWindow::DisposeResourceMap( void ); +}; + +#endif \ No newline at end of file diff --git a/Carbon/Classes/Utility.cpp b/Carbon/Classes/Utility.cpp new file mode 100644 index 0000000..f00f2db --- /dev/null +++ b/Carbon/Classes/Utility.cpp @@ -0,0 +1,220 @@ +#include "Utility.h" +extern globals g; + + /**********************/ + /* QUICKDRAW ROUTINES */ +/**********************/ + +/*** SET COLOUR ***/ +void SetColour( RGBColor *colour, UInt16 red, UInt16 green, UInt16 blue ) +{ + colour->red = red; + colour->green = green; + colour->blue = blue; +} + +/* investigate the call ShieldCursor() - it hides the mouse when it enters a certain rect */ + + /*******************/ + /* STRING ROUTINES */ +/*******************/ + +/*** C STRING LENGTH ***/ +unsigned long CStringLength( char *string ) +{ + unsigned long length; + Boolean end = false; + for( length = 0; end == false; length++ ) + if( *(string + length) == 0x00 ) end = true; + return length; +} + +/*** PASCAL STRING LENGTH ***/ +unsigned char PStringLength( unsigned char *string ) +{ + return *string; +} + +/*** TYPE TO C STRING ***/ +void TypeToCString( const OSType type, char *string ) +{ + BlockMoveData( &type, &string[0], sizeof(OSType) ); + string[sizeof(OSType)] = 0x00; +} + +/*** TYPE TO PASCAL STRING ***/ +void TypeToPString( const OSType type, Str255 string ) +{ + string[0] = sizeof(OSType); + BlockMoveData( &type, &string[1], sizeof(OSType) ); +} + +/*** TYPE TO CORE FOUNDATION STRING ***/ +void TypeToCFString( const OSType type, CFStringRef *string ) +{ + char cString[5]; + TypeToCString( type, (char *) &cString ); + *string = CFStringCreateWithCString( CFAllocatorGetDefault(), (char *) &cString, kCFStringEncodingMacRoman ); +} + +/*** COPY C STRING ***/ +void CopyCString( const UInt8 *source, UInt8 *dest ) +{ +//#pragma warning off + while( *dest++ = *source++ ); +//#pragma warning reset +} + +/*** COPY PASCAL STRING ***/ +void CopyPString( const UInt8 *source, UInt8 *dest ) +{ + UInt8 length = *source, count; + for( count = 0; count <= length; count++ ) + *dest++ = *source++; +} + +/*** EQUAL C STRINGS ***/ +Boolean EqualCStrings( UInt8 *source, UInt8 *dest ) +{ + while( *source != 0x00 ) + if( *source++ != *dest++ ) return false; + return true; +} + +/*** EQUAL PASCAL STRINGS ***/ +Boolean EqualPStrings( UInt8 *source, UInt8 *dest ) +{ + UInt8 length = *source, count; + for( count = 0; count <= length; count++ ) + if( *source++ != *dest++ ) return false; + return true; +} + +/*** APPEND ONE PASCAL STRING ONTO ANOTHER ***/ +void AppendPString( Str255 original, ConstStr255Param added ) +{ + short numberBytes = added[0]; // length of string to be added + short totalLength = added[0] + original[0]; + + if( totalLength > 255 ) // too long, adjust number of bytes to add + { + totalLength = 255; + numberBytes = totalLength - original[0]; + } + + BlockMoveData( &added[1], &original[totalLength-numberBytes + 1], numberBytes ); + original[0] = totalLength; // new length of original string + while( ++totalLength <= 255 ) + original[totalLength] = 0x00; // set rest of string to zero +} + + /*****************/ + /* MENU ROUTINES */ +/*****************/ + +/*** MENU ITEM ENABLE ***/ +void MenuItemEnable( MenuRef menu, MenuItemIndex item, Boolean enable ) +{ +#if !TARGET_API_MAC_CARBON + if( g.systemVersion >= kMacOSEightPointFive ) + { +#endif + if( enable ) EnableMenuItem( menu, item ); + else DisableMenuItem( menu, item ); +#if !TARGET_API_MAC_CARBON + } + else + { + if( enable ) EnableItem( menu, item ); + else DisableItem( menu, item ); + } +#endif +} + +/*** ENABLE MENU COMMAND ***/ +void EnableCommand( MenuRef menu, MenuCommand command, Boolean enable ) +{ + if( enable ) EnableMenuCommand( menu, command ); + else DisableMenuCommand( menu, command ); +} + + /******************/ + /* FILE UTILITIES */ +/******************/ + +/* MakeRelativeAliasFile creates a new alias file located at + aliasDest referring to the targetFile. relative path + information is stored in the new file. */ + +/* MAKE RELATIVE ALIAS FILE */ +OSErr MakeRelativeAliasFile(FSSpec *targetFile, FSSpec *aliasDest) +{ + OSErr error; + FInfo fndrInfo; + AliasHandle theAlias = null; + Boolean fileCreated = false; + SInt16 rsrc = -1; + + // set up our the alias' file information + error = FSpGetFInfo( targetFile, &fndrInfo ); + if( error != noErr ) goto bail; + if( fndrInfo.fdType == 'APPL') + fndrInfo.fdType = kApplicationAliasType; + fndrInfo.fdFlags = kIsAlias; // implicitly clear the inited bit + + // create the new file + FSpCreateResFile( aliasDest, 'TEMP', 'TEMP', smSystemScript ); + if( (error = ResError() ) != noErr) goto bail; + fileCreated = true; + + // set the file information or the new file + error = FSpSetFInfo( aliasDest, &fndrInfo ); + if( error != noErr ) goto bail; + + // create the alias record, relative to the new alias file + error = NewAlias( aliasDest, targetFile, &theAlias ); + if( error != noErr ) goto bail; + + // save the resource + rsrc = FSpOpenResFile( aliasDest, fsRdWrPerm ); + if( rsrc == -1) + { + error = ResError(); + goto bail; + } + UseResFile( rsrc ); + AddResource( (Handle) theAlias, rAliasType, 0, aliasDest->name ); + error = ResError(); + if( error != noErr) goto bail; + theAlias = null; + CloseResFile( rsrc ); + rsrc = -1; + error = ResError(); + if( error != noErr) goto bail; + + // done + return noErr; +bail: + if( rsrc != -1 ) CloseResFile( rsrc ); + if( fileCreated ) FSpDelete( aliasDest ); + if( theAlias != null ) DisposeHandle( (Handle) theAlias ); + return error; +} + + /**********************/ + /* INTERNET UTILITIES */ +/**********************/ + +/*** LAUNCH WEB BROWSER ***/ +OSStatus LaunchURL( char *url ) +{ + OSStatus error = noErr; + ICInstance instance; + error = ICStart( &instance, kResKnifeCreator ); + if( error != noErr ) return error; + + SInt32 start = 0, length = CStringLength( url ); + error = ICLaunchURL( instance, null, url, length, &start, &length ); + ICStop( instance ); + return error; +} \ No newline at end of file diff --git a/Carbon/Classes/Utility.h b/Carbon/Classes/Utility.h new file mode 100644 index 0000000..0bb8a00 --- /dev/null +++ b/Carbon/Classes/Utility.h @@ -0,0 +1,73 @@ +#include "ResKnife.h" + +#ifndef _ResKnife_Utility_ +#define _ResKnife_Utility_ + +/*! + @header Utility Routines + @discussion Supplies the application with generic oft-used functions such as string conversion and menu enabling. +*/ + +/*! + @function SetColour +*/ +void SetColour( RGBColor *colour, UInt16 red, UInt16 green, UInt16 blue ); +/*! + @function CStringLength +*/ +unsigned long CStringLength( char *string ); +/*! + @function PStringLength +*/ +unsigned char PStringLength( unsigned char *string ); +/*! + @function TypeToCString +*/ +void TypeToCString( const OSType type, char *string ); +/*! + @function TypeToPString +*/ +void TypeToPString( const OSType type, Str255 string ); +/*! + @function TypeToCFString +*/ +void TypeToCFString( const OSType type, CFStringRef *string ); +/*! + @function CopyCString +*/ +void CopyCString( const UInt8 *source, UInt8 *dest ); +/*! + @function CopyPString +*/ +void CopyPString( const UInt8 *source, UInt8 *dest ); +/*! + @function EqualCStrings +*/ +Boolean EqualCStrings( UInt8 *source, UInt8 *dest ); +/*! + @function EqualPStrings +*/ +Boolean EqualPStrings( UInt8 *source, UInt8 *dest ); +/*! + @function AppendPString +*/ +void AppendPString( Str255 original, ConstStr255Param added ); +/*! + @function MenuItemEnable +*/ +void MenuItemEnable( MenuRef menu, MenuItemIndex item, Boolean enable ); +/*! + @function EnableCommand +*/ +void EnableCommand( MenuRef menu, MenuCommand command, Boolean enable ); +/*! + @function MakeRelativeAliasFile +*/ +OSErr MakeRelativeAliasFile(FSSpec *targetFile, FSSpec *aliasDest); +/*! + @function LaunchURL + @param url A C string containing the address to which you want the user to go. You must include 'http://' if necessary, and all addresses to a directory should have a trailing slash. +*/ +OSStatus LaunchURL( char *url ); + +#endif \ No newline at end of file diff --git a/Carbon/Classes/WindowObject.cpp b/Carbon/Classes/WindowObject.cpp new file mode 100644 index 0000000..98699c5 --- /dev/null +++ b/Carbon/Classes/WindowObject.cpp @@ -0,0 +1,75 @@ +#include "WindowObject.h" + +/*** CONSTRUCTOR ***/ +WindowObject::WindowObject( void ) +{ + memset( this, 0, sizeof(WindowObject) ); +} + +/*** DESTRUCTOR ***/ +WindowObject::~WindowObject( void ) +{ + if( window ) + { + HideWindow( window ); + DisposeWindow( window ); + } +} + +/*** WINDOW ACCESSOR ***/ +WindowRef WindowObject::Window( void ) +{ + return window; +} + +/*** WINDOW BOUNDS ARE CHANGING ***/ +OSStatus WindowObject::BoundsChanging( EventRef event ) +{ + #pragma unused( event ) + return eventNotHandledErr; +} + +/*** WINDOW BOUNDS HAVE CHANGED ***/ +OSStatus WindowObject::BoundsChanged( EventRef event ) +{ + #pragma unused( event ) + return eventNotHandledErr; +} + +#if !TARGET_API_MAC_CARBON + +/*** CLOSE ***/ +OSStatus WindowObject::Close( void ) +{ + delete this; + return noErr; +} + +/*** ACTIVATE ***/ +OSStatus WindowObject::Activate( Boolean active ) +{ + #pragma unused( active ) + return eventNotHandledErr; +} + +/*** UPDATE ***/ +OSStatus WindowObject::Update( RgnHandle region ) +{ + #pragma unused( region ) + return eventNotHandledErr; +} + +/*** UPDATE SCROLL BARS ***/ +OSStatus WindowObject::UpdateScrollBars( void ) +{ + return eventNotHandledErr; +} + +/*** MOUSE CLICK ***/ +OSStatus WindowObject::Click( Point mouse, EventModifiers modifiers ) +{ + #pragma unused( mouse, modifiers ) + return eventNotHandledErr; +} + +#endif diff --git a/Carbon/Classes/WindowObject.h b/Carbon/Classes/WindowObject.h new file mode 100644 index 0000000..18d98d2 --- /dev/null +++ b/Carbon/Classes/WindowObject.h @@ -0,0 +1,86 @@ +#include "ResKnife.h" + +#ifndef _ResKnife_WindowObject_ +#define _ResKnife_WindowObject_ + +/*! + * @header WindowObject + * @discussion The base class for all windows in the program. Also declares the (very simple) PlugWindow subclass. + */ + +/*! + * @class WindowObject + * @abstract Base class for all windows in the program. + * @discussion Mainly consisting of regular controls such as scroll bars and headers, this doesn't do much by it's self. + */ +class WindowObject +{ + // data +protected: +/*! @var window Stores the Mac OS WindowRef pertaining to this window. */ + WindowRef window; +/*! @var header Header control (the grey bar at the top of most windows). */ + ControlRef header; +/*! @var leftText Left hand side static text control (in the header). */ + ControlRef left; +/*! @var rightText Right hand side static text control (in the header). */ + ControlRef right; +#if !TARGET_API_MAC_CARBON +/*! @var horizScroll Horizontal scrollbar at the bottom of most windows */ + ControlRef horizScroll; +/*! @var vertScroll Vetical scrollbar down the right side of most windows */ + ControlRef vertScroll; +/*! @var themeSavvy True if this window is using an Appearance Manager WDEF. (Also used to determine if Appearance controls should be drawn if this window.) */ + Boolean themeSavvy; +#endif + +public: + // methods +/*! + * @function WindowObject + * @discussion Constructor function. + */ + WindowObject( void ); +/*! + * @function WindowObject + * @discussion Desturctor function. + */ + virtual ~WindowObject( void ); +/*! + * @function Window + * @discussion Accessor for the object’s WindowRef. + */ + virtual WindowRef Window( void ); +/*! + * @function BoundsChanging + */ + virtual OSStatus BoundsChanging( EventRef event ); +/*! + * @function BoundsChanged + */ + virtual OSStatus BoundsChanged( EventRef event ); +#if !TARGET_API_MAC_CARBON +/*! + * @function Close + */ + virtual OSStatus Close( void ); +/*! + * @function Activate + */ + virtual OSStatus Activate( Boolean active = true ); +/*! + * @function Update + */ + virtual OSStatus Update( RgnHandle region = null ); +/*! + * @function UpdateScrollBars + */ + virtual OSStatus UpdateScrollBars( void ); +/*! + * @function Click + */ + virtual OSStatus Click( Point mouse, EventModifiers modifiers ); +#endif +}; + +#endif diff --git a/Carbon/Generic.h b/Carbon/Generic.h new file mode 100644 index 0000000..b1eb58c --- /dev/null +++ b/Carbon/Generic.h @@ -0,0 +1,16 @@ +// abbreviations +#define null NULL +#define qdb qd.screenBits.bounds + +// Easier API call names +#define GetWindowRefCon( window ) (long) GetWRefCon( window ) +#define SetWindowRefCon( window, refcon ) SetWRefCon( window, refcon ) +#define GetWindowTitle( window, string ) GetWTitle( window, string ) +#define SetWindowTitle( window, name ) SetWTitle( window, name ) +#define InvalidateRect( bounds ) InvalRect( bounds ) +#define InvalidateWindowRect( window, bounds ) (OSStatus) InvalWindowRect( window, bounds ) +#define RectToRegion( region, rect ) RectRgn( region, rect ) +#define NewPoint() (Point) { 0, 0 } +#define SetPoint( point, x, y ) SetPt( point, x, y ) +#define HilightColour( colour ) HiliteColor( colour ) +#define GetPortHilightColour( window, colour ) GetPortHiliteColor( window, colour ) diff --git a/Carbon/ResKnife.h b/Carbon/ResKnife.h new file mode 100644 index 0000000..f651f7e --- /dev/null +++ b/Carbon/ResKnife.h @@ -0,0 +1,310 @@ +/* + +With thanks to: For: + Jim Luther MoreFiles + Bryan K. Ressler & Bradley D. Mohr Asynchronous SoundHelper + John Montbriand & Pete Gontier FinderDragPro + +*/ + +// interesting function I found: CFBundleOpenBundleResourceMap() + +#if defined(__MWERKS__) // compiling with CodeWarrior + #if __profile__ + #include + #endif +#else // compiling with ProjectBuilder, use frameworks + #define NO_DATA_BROWSER_TWEAKS 0 + #define USE_OLD_DATA_BROWSER_STRUCTS 0 + #include +#endif + +#ifndef _ResKnife_ +#define _ResKnife_ + +/*! + * @header ResKnife Global Header + * Imported by all ResKnife's C++ source files, this defines various structures and constants which have an application-wide domain. + */ + +// compile options +#if TARGET_API_MAC_CARBON + #define USE_NIBS 0 // toggle this +#else + #define USE_NIBS 0 // leave this set to zero +#endif + +// include my generic API abbreviations +#include "Generic.h" + +/*** STRUCTURES ***/ + +// type definitions +typedef class ResourceObject ResourceObject, *ResourceObjectPtr; +typedef class PlugObject PlugObject, *PlugObjectPtr; +typedef class WindowObject WindowObject, *WindowObjectPtr; +typedef class FileWindow FileWindow, *FileWindowPtr; +typedef class PlugWindow PlugWindow, *PlugWindowPtr; +typedef class PickerWindow PickerWindow, *PickerWindowPtr; +typedef class EditorWindow EditorWindow, *EditorWindowPtr; +typedef class InspectorWindow InspectorWindow, *InspectorWindowPtr; + +/* Global Variables */ +struct globals +{ + // application + Str255 appName; + Str255 prefsName; + Boolean quitting; + Boolean cancelQuit; + Boolean frontApp; + Boolean asyncSound; // async sound initalised + Boolean callSH; // call sound idle + short appResFile; + Handle emergencyMemory; + EventLoopTimerRef idleTimer; // for SHIdle() + + // system info + SInt32 systemVersion; + SInt32 carbonVersion; + Boolean dragAvailable; + Boolean translucentDrag; + Boolean navAvailable; + Boolean appearanceAvailable; + Boolean windowMgrAvailable; + Boolean extendedWindowAttr; + + // files + UInt16 tempCount; // number of temporary files opened, so names don't clash + + // dialogs + DialogPtr prefsDialog; + Boolean protectPrefs; // if newer version of prefs file exists, will not overwrite + InspectorWindowPtr inspector; + + // colours + RGBColor white; // 0xFFFF, 65535 + RGBColor bgColour; // 0xEEEE, 61166 + RGBColor sortColour; // 0xDDDD, 56797 + RGBColor bevelColour; // 0xAAAA, 43690 + RGBColor textColour; // 0x7777, 30583 + RGBColor frameColour; // 0x5555, 21845 + RGBColor black; // 0x0000, 0 + + // debugging + Boolean debug; + Boolean surpressErrors; + Boolean useAppleEvents; + Boolean useAppearance; + Boolean useNavServices; + Boolean useSheets; // OS X only +}; + +/*! + * @struct prefs + * @abstract Appplication-wide user preferences + * @discussion Stores all user preferences in memory to avoid needless disk access. This structure is simply written straight to disk when the preferences are saved. + * @field version Identifies which version of ResKnife saved the prefs file (allowing future versions to parse the data contained). + */ +struct prefs +{ + UInt32 version; // == kResKnifeCurrentVersion, when saved to disk allows older prefs to be read in + Boolean quitIfNoWindowsAreOpen; // silly name! - perhaps grandmaMode ? + Boolean autoSave; + UInt32 autoSaveInterval; // should be in units of time + Boolean warnOnDelete; // "Are you sure?" dialog, © Microsoft 1992-2000 +}; + +/*** CONSTANTS ***/ + +// Mac OS versions +const SInt32 kMacOS71 = 0x00000710; +const SInt32 kMacOS755 = 0x00000755; +const SInt32 kMacOS8 = 0x00000800; +const SInt32 kMacOS85 = 0x00000850; +const SInt32 kMacOS86 = 0x00000860; +const SInt32 kMacOS9 = 0x00000900; +const SInt32 kMacOS904 = 0x00000904; +const SInt32 kMacOS91 = 0x00000910; +const SInt32 kMacOS921 = 0x00000921; +const SInt32 kMacOS10 = 0x00001000; +const SInt32 kMacOS101 = 0x00001010; +const SInt32 kMacOSX = kMacOS10; + +// CarbonLib versions +const SInt32 kCarbonLib104 = 0x00000104; +const SInt32 kCarbonLib11 = 0x00000110; +const SInt32 kCarbonLib12 = 0x00000120; +const SInt32 kCarbonLib125 = 0x00000125; +const SInt32 kCarbonLib131 = 0x00000131; +const SInt32 kCarbonLib14 = 0x00000140; +const SInt32 kCarbonLib145 = 0x00000145; + +// ResKnife version & file types +const UInt32 kCurrentVersion = 0x00040001; +const UInt32 kResKnifeCreator = FOUR_CHAR_CODE('ResK'); +const UInt32 kResourceFileType = FOUR_CHAR_CODE('rsrc'); +const UInt32 kResourceTransferType = FOUR_CHAR_CODE('rsrc'); // for copy/paste and drags + +// memory +const UInt16 kMinimumFreeMemory = 20 * 1024; // if we have over 20 KB we're alright +const UInt16 kEmergencyMemory = 40 * 1024; // 40 KB are put aside for emergencies + +// window kinds +enum WindowKind +{ + kFileWindowKind = 1, + kPickerWindowKind, + kEditorWindowKind, + kInspectorWindowKind +}; + +// control sizes +const UInt16 kScrollBarWidth = 16; + +/* RESOURCES */ + +/*! + * @enum Menu Resources + * @discussion Contains all resource IDs for menu items and all ascociated item numbers. + */ +enum // menus +{ + kClassicMenuBar = 128, + + kAppleMenu = 128, + kAppleMenuAboutItem = 1, + + kFileMenu = 129, + kFileMenuNewFileItem = 1, + kFileMenuOpenFileItem, + kFileMenuCloseWindowItem, + kFileMenuQuitItem = 12, + + kEditMenu = 130, + kEditMenuClearItem = 7, + kEditMenuPreferencesItem = 13, + + kResourceMenu = 131, + kResourceMenuNewResource = 1, + + kWindowMenu = 132, + + kDebugMenu = 200, + kDebugMenuDebugItem = 1, + kDebugMenuSurpressErrorsItem = 3, + kDebugMenuAppleEventsItem, + kDebugMenuAppearanceItem, + kDebugMenuNavServicesItem, + kDebugMenuSheetsItem +}; + +enum // application menu +{ + kMenuCommandAbout = FOUR_CHAR_CODE('abou') +}; + +enum // file menu +{ + kMenuCommandNewFile = FOUR_CHAR_CODE('new '), + kMenuCommandOpenFile = FOUR_CHAR_CODE('open'), + kMenuCommandCloseWindow = FOUR_CHAR_CODE('clos'), + kMenuCommandCloseFile = FOUR_CHAR_CODE('clsf'), + kMenuCommandSaveFile = FOUR_CHAR_CODE('save'), + kMenuCommandSaveFileAs = FOUR_CHAR_CODE('svas'), + kMenuCommandRevertFile = FOUR_CHAR_CODE('rvtf'), + kMenuCommandPageSetup = FOUR_CHAR_CODE('setu'), + kMenuCommandPrint = FOUR_CHAR_CODE('prin') +}; + +enum // edit menu +{ + kMenuCommandFind = FOUR_CHAR_CODE('find'), + kMenuCommandFindAgain = FOUR_CHAR_CODE('agin') +}; + +enum // resource menu +{ + kMenuCommandNewResource = FOUR_CHAR_CODE('newr'), + kMenuCommandOpenHex = FOUR_CHAR_CODE('hex '), + kMenuCommandOpenDefault = FOUR_CHAR_CODE('edit'), + kMenuCommandOpenTemplate = FOUR_CHAR_CODE('tmpl'), + kMenuCommandOpenSpecific = FOUR_CHAR_CODE('tmp '), + kMenuCommandRevertResource = FOUR_CHAR_CODE('rvtr'), + kMenuCommandPlaySound = FOUR_CHAR_CODE('play') +}; + +enum // debug menu +{ + kMenuCommandDebug = FOUR_CHAR_CODE('dbug'), + kMenuCommandSurpressErrors = FOUR_CHAR_CODE('surp'), + kMenuCommandAppleEvents = FOUR_CHAR_CODE('appl'), + kMenuCommandAppearance = FOUR_CHAR_CODE('appr'), + kMenuCommandNavServices = FOUR_CHAR_CODE('nav '), + kMenuCommandSheets = FOUR_CHAR_CODE('shet') +}; + +enum // windows +{ + kFileWindow7 = 128, + kFileWindow8 = 129 +}; + +enum // dialogs +{ + kErrorDialog = 128, + kNewResourceDialog = 129 +}; + +enum // controls +{ + kSystem7ScrollBarControl = 128, + kAppearanceScrollBarControl = 129, + kNormalHeaderControl = 130, + kFileHeaderControl = 131, + kEditTextControl = 132 +}; + +enum // icons +{ + kSortUpIcon = 921, + kSortDownIcon = 922, + kDefaultResourceIcon = 1000 +}; + +enum // strings +{ + kErrorStrings = 128, + kStringUnknownError = 1, + kExplanationUnknownError, + kStringOSNotGoodEnough, + kExplanationOSNotGoodEnough, + kStringMinimumCarbonLib, + kExplanationMinimumCarbonLib, + kStringRecommendedCarbonLib, + kExplanationRecommendedCarbonLib, + + kDebugStrings = 129, + kStringRFNotFound = 1, + kExplanationRFNotFound, + kStringDFNotFound, + kExplanationDFNotFound, + + kFileNameStrings = 130, + kStringResKnifeName = 1, + kStringPrefsFileName, + kStringNewDragFileName, + + kWindowNameStrings = 131, + kStringNewFile = 1, + kStringPrefsWindowName, + kStringInspectorWindowName, + kStringNewResourceDialogName, + + kResourceNameStrings = 132, + kStringDataFork = 1, + kStringUntitledResource, + kStringCustomIcon +}; + +#endif diff --git a/Carbon/ResKnife.lib b/Carbon/ResKnife.lib new file mode 100755 index 0000000000000000000000000000000000000000..071badbe036378f639aebc2aa44fcf975f7f3f03 GIT binary patch literal 74319 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 literal 0 HcmV?d00001 diff --git a/Carbon/Resources/Carbon.r b/Carbon/Resources/Carbon.r new file mode 100644 index 0000000..90fa43f --- /dev/null +++ b/Carbon/Resources/Carbon.r @@ -0,0 +1 @@ +#include "MacTypes.r" /*** CARBON RESOURCES ***/ data 'carb' ( 0 ) { $"00000000" }; data 'plst' ( 0 ) { "\n" "\n" "\n" "\n" " CFBundleDevelopmentRegion\n" " English\n" " CFBundleDocumentTypes\n" " \n" " \n" " CFBundleTypeExtensions\n" " \n" " rsrc\n" " \n" " CFBundleTypeIconFile\n" " 129\n" " CFBundleTypeName\n" " Resource file\n" " CFBundleTypeOSTypes\n" " \n" " rsrc\n" " RSRC\n" " \n" " CFBundleTypeRole\n" " Editor\n" " \n" " \n" " CFBundleTypeExtensions\n" " \n" " icns\n" " \n" " CFBundleTypeIconFile\n" " 129\n" " CFBundleTypeName\n" " Icon file\n" " CFBundleTypeOSTypes\n" " \n" " icns\n" " \n" " CFBundleTypeRole\n" " Editor\n" " \n" " \n" " CFBundleExecutable\n" " ResKnife (Carbon)\n" " CFBundleGetInfoString\n" " A resource editor for Mac OS X\n" " CFBundleIconFile\n" " 128\n" " CFBundleIdentifier\n" " com.nickshanks.resknife\n" " CFBundleInfoDictionaryVersion\n" " 6.0\n" " CFBundleName\n" " ResKnife\n" " CFBundlePackageType\n" " APPL\n" " CFBundleShortVersionString\n" " Development version 0.4d1\n" " CFBundleSignature\n" " ResK\n" " CFBundleVersion\n" " 0.4d1\n" " CSResourcesFileMapped\n" " \n" "\n" "" }; \ No newline at end of file diff --git a/Carbon/Resources/ResKnife.nib/classes.nib b/Carbon/Resources/ResKnife.nib/classes.nib new file mode 100644 index 0000000..1a291cd --- /dev/null +++ b/Carbon/Resources/ResKnife.nib/classes.nib @@ -0,0 +1,295 @@ +{ + IBClasses = ( + {CLASS = FirstResponder; LANGUAGE = ObjC; SUPERCLASS = NSObject; }, + {CLASS = IBCarbonBevelButton; LANGUAGE = ObjC; SUPERCLASS = IBCarbonControl; }, + {CLASS = IBCarbonButton; LANGUAGE = ObjC; SUPERCLASS = IBCarbonControl; }, + { + CLASS = IBCarbonChasingArrows; + LANGUAGE = ObjC; + OUTLETS = { + carbonBounds = id; + command = id; + controlHandle = id; + controlID = id; + controlMaximum = id; + controlMinimum = id; + controlName = id; + controlSignature = id; + controlSize = id; + controlTitle = id; + controlValue = id; + enabled = id; + hidden = id; + isEnabled = id; + isHidden = id; + objectNameForInspectorTitle = id; + targetFrameworkName = id; + targetFrameworkSupportsConnections = id; + titleRect = id; + }; + SUPERCLASS = IBCarbonControl; + }, + {CLASS = IBCarbonCheckBox; LANGUAGE = ObjC; SUPERCLASS = IBCarbonControl; }, + { + CLASS = IBCarbonClockDate; + LANGUAGE = ObjC; + OUTLETS = { + carbonBounds = id; + clockType = id; + command = id; + controlHandle = id; + controlID = id; + controlMaximum = id; + controlMinimum = id; + controlName = id; + controlSignature = id; + controlSize = id; + controlTitle = id; + controlValue = id; + enabled = id; + hidden = id; + isEditable = id; + isEnabled = id; + isHidden = id; + isLive = id; + objectNameForInspectorTitle = id; + targetFrameworkName = id; + targetFrameworkSupportsConnections = id; + titleRect = id; + }; + SUPERCLASS = IBCarbonControl; + }, + { + CLASS = IBCarbonControl; + LANGUAGE = ObjC; + OUTLETS = { + carbonBounds = id; + command = id; + controlHandle = id; + controlID = id; + controlMaximum = id; + controlMinimum = id; + controlName = id; + controlSignature = id; + controlSize = id; + controlTitle = id; + controlValue = id; + enabled = id; + hidden = id; + isEnabled = id; + isHidden = id; + objectNameForInspectorTitle = id; + targetFrameworkName = id; + targetFrameworkSupportsConnections = id; + titleRect = id; + }; + SUPERCLASS = NSObject; + }, + { + CLASS = IBCarbonDBListView; + LANGUAGE = ObjC; + SUPERCLASS = IBCarbonDataBrowser; + }, + {CLASS = IBCarbonDataBrowser; LANGUAGE = ObjC; SUPERCLASS = IBCarbonControl; }, + {CLASS = IBCarbonEditText; LANGUAGE = ObjC; SUPERCLASS = IBCarbonControl; }, + { + CLASS = IBCarbonGroupBox; + LANGUAGE = ObjC; + OUTLETS = { + autoToggle = id; + carbonBounds = id; + command = id; + contentRect = id; + controlHandle = id; + controlID = id; + controlMaximum = id; + controlMinimum = id; + controlName = id; + controlSignature = id; + controlSize = id; + controlTitle = id; + controlValue = id; + enabled = id; + groupBoxTitleType = id; + groupBoxType = id; + hasVariableWidth = id; + hidden = id; + isAutoToggle = id; + isEnabled = id; + isHidden = id; + isPrimary = id; + objectNameForInspectorTitle = id; + primary = id; + selectedItem = id; + supportsInsideOutSelection = id; + targetFrameworkName = id; + targetFrameworkSupportsConnections = id; + titleJustification = id; + titleRect = id; + variableWidth = id; + }; + SUPERCLASS = IBCarbonControl; + }, + { + CLASS = IBCarbonIcon; + LANGUAGE = ObjC; + OUTLETS = { + carbonBounds = id; + command = id; + contentResID = id; + contentType = id; + controlHandle = id; + controlID = id; + controlMaximum = id; + controlMinimum = id; + controlName = id; + controlSignature = id; + controlSize = id; + controlTitle = id; + controlValue = id; + dontTrack = id; + enabled = id; + hidden = id; + isEnabled = id; + isHidden = id; + objectNameForInspectorTitle = id; + targetFrameworkName = id; + targetFrameworkSupportsConnections = id; + titleRect = id; + }; + SUPERCLASS = IBCarbonControl; + }, + {CLASS = IBCarbonImageWell; LANGUAGE = ObjC; SUPERCLASS = IBCarbonControl; }, + { + CLASS = IBCarbonLittleArrows; + LANGUAGE = ObjC; + OUTLETS = { + carbonBounds = id; + command = id; + controlHandle = id; + controlID = id; + controlMaximum = id; + controlMinimum = id; + controlName = id; + controlSignature = id; + controlSize = id; + controlTitle = id; + controlValue = id; + enabled = id; + hidden = id; + increment = id; + initialValue = id; + isEnabled = id; + isHidden = id; + maximumValue = id; + minimumValue = id; + objectNameForInspectorTitle = id; + targetFrameworkName = id; + targetFrameworkSupportsConnections = id; + titleRect = id; + }; + SUPERCLASS = IBCarbonControl; + }, + {CLASS = IBCarbonMenu; LANGUAGE = ObjC; SUPERCLASS = NSMenu; }, + {CLASS = IBCarbonMenuItem; LANGUAGE = ObjC; SUPERCLASS = NSMenuItem; }, + {CLASS = IBCarbonPopupButton; LANGUAGE = ObjC; SUPERCLASS = IBCarbonControl; }, + {CLASS = IBCarbonRadioGroup; LANGUAGE = ObjC; SUPERCLASS = IBCarbonControl; }, + {CLASS = IBCarbonRelevanceBar; LANGUAGE = ObjC; SUPERCLASS = IBCarbonControl; }, + {CLASS = IBCarbonRootControl; LANGUAGE = ObjC; SUPERCLASS = IBCarbonControl; }, + {CLASS = IBCarbonRoundButton; LANGUAGE = ObjC; SUPERCLASS = IBCarbonControl; }, + {CLASS = IBCarbonScrollBar; LANGUAGE = ObjC; SUPERCLASS = IBCarbonControl; }, + { + CLASS = IBCarbonSeparator; + LANGUAGE = ObjC; + OUTLETS = { + carbonBounds = id; + command = id; + controlHandle = id; + controlID = id; + controlMaximum = id; + controlMinimum = id; + controlName = id; + controlSignature = id; + controlSize = id; + controlTitle = id; + controlValue = id; + enabled = id; + hidden = id; + isEnabled = id; + isHidden = id; + objectNameForInspectorTitle = id; + targetFrameworkName = id; + targetFrameworkSupportsConnections = id; + titleRect = id; + }; + SUPERCLASS = IBCarbonControl; + }, + { + CLASS = IBCarbonSlider; + LANGUAGE = ObjC; + OUTLETS = { + carbonBounds = id; + command = id; + controlHandle = id; + controlID = id; + controlMaximum = id; + controlMinimum = id; + controlName = id; + controlSignature = id; + controlSize = id; + controlTitle = id; + controlValue = id; + enabled = id; + hidden = id; + initialValue = id; + isEnabled = id; + isHidden = id; + isLive = id; + maximumValue = id; + minimumValue = id; + numTickMarks = id; + objectNameForInspectorTitle = id; + orientation = id; + targetFrameworkName = id; + targetFrameworkSupportsConnections = id; + titleRect = id; + }; + SUPERCLASS = IBCarbonControl; + }, + {CLASS = IBCarbonStaticText; LANGUAGE = ObjC; SUPERCLASS = IBCarbonControl; }, + {CLASS = IBCarbonTab; LANGUAGE = ObjC; SUPERCLASS = IBCarbonControl; }, + { + CLASS = IBCarbonTriangle; + LANGUAGE = ObjC; + OUTLETS = { + autoToggle = id; + carbonBounds = id; + collapsed = id; + command = id; + controlHandle = id; + controlID = id; + controlMaximum = id; + controlMinimum = id; + controlName = id; + controlSignature = id; + controlSize = id; + controlTitle = id; + controlValue = id; + drawTitle = id; + enabled = id; + hidden = id; + isCollapsed = id; + isEnabled = id; + isHidden = id; + objectNameForInspectorTitle = id; + orientation = id; + targetFrameworkName = id; + targetFrameworkSupportsConnections = id; + titleRect = id; + }; + SUPERCLASS = IBCarbonControl; + }, + {CLASS = IBCarbonUserPane; LANGUAGE = ObjC; SUPERCLASS = IBCarbonControl; } + ); + IBVersion = 1; +} \ No newline at end of file diff --git a/Carbon/Resources/ResKnife.nib/info.nib b/Carbon/Resources/ResKnife.nib/info.nib new file mode 100644 index 0000000..b704a94 --- /dev/null +++ b/Carbon/Resources/ResKnife.nib/info.nib @@ -0,0 +1,50 @@ + + + + + IBDocumentLocation + 70 160 588 603 0 73 1152 775 + IBMainMenuLocation + 157 692 404 44 0 72 1024 674 + IBUserGuides + + About Box + + guideLocations + + guidesLocked + NO + + File Window + + guideLocations + + guidesLocked + NO + + Inspector + + guideLocations + + guidesLocked + NO + + New Resource + + guideLocations + + guidesLocked + NO + + Preferences + + guideLocations + + guidesLocked + NO + + + targetFramework + IBCarbonFramework + + diff --git a/Carbon/Resources/ResKnife.nib/objects.xib b/Carbon/Resources/ResKnife.nib/objects.xib new file mode 100644 index 0000000..c8e96a6 --- /dev/null +++ b/Carbon/Resources/ResKnife.nib/objects.xib @@ -0,0 +1,1023 @@ + + + IBCarbonFramework + + NSApplication + + + + ResKnife + + + ResKnife + + ResKnife + + + About ResKnife + 0 + abou + + + Plug-ins + + Plug-ins + + + Organise Plug-ins + plug + + + TRUE + + + Hex Editor Prefs + + + Dialog Editor Prefs + + + + + + _NSAppleMenu + + + + File + + File + + + New + n + new + + + Open... + o + open + + + TRUE + + + TRUE + Close + W + clsf + + + Save + s + save + + + Save As... + S + sava + Not yet implemented + + + Revert File… + r + rvtf + Not yet implemented + + + TRUE + + + Page Setup... + P + setu + Not yet implemented + + + Print... + p + prin + Not yet implemented + + + TRUE + + + + Edit + + Edit + + + Undo + z + undo + Not yet implemented + + + Redo + Z + redo + Not yet implemented + + + TRUE + + + Cut + x + cut + + + Copy + c + copy + + + Paste + v + past + + + Delete + 0 + clea + + + Select All + a + sall + + + TRUE + + + + Resource + + Resource + + + Create New Resource… + k + newr + + + TRUE + + + TRUE + Open Using Default Editor + e + edit + + + Open Using Default Template + E + tmpl + + + Open Using Specific Template… + E + 1572864 + tmp + + + TRUE + Open Using Hexidecimal Editor + e + 1572864 + hex + + + TRUE + + + Revert Resource… + R + rvtr + + + Play Sound + t + play + + + TRUE + + + + Debug + + Debug + + + Debug Program + d + bugs + + + TRUE + + + Surpress Errors + surp + + + Use Sheets + shet + + + + + + Window + + Window + + + Close Window + w + + + Zoom Window + + + Minimize Window + m + + + TRUE + + + Bring All to Front + + + _NSWindowsMenu + TRUE + + + + Help + + Help + + + ResKnife Help + ? + Turn help off + + + TRUE + + + + _NSMainMenu + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 53 8 413 488 + New Resource File + + 0 0 360 480 + + + 21 0 360 480 + brow + FALSE + + + name + ticn + 458752 + 1 + 100 + 300 + 0 + Name + + + type + 1 + 60 + 60 + -1 + Type + + + id + 80 + 80 + -1 + ID + + + size + 100 + -1 + Size + + + + + 2 5 18 240 + left + left hand side message + + + 2 240 18 475 + left + right hand side message + + + + TRUE + 6 + 0 + + + + + + + + + + + + + + + + + + + + + + 134 325 434 725 + Create A New Resource + + 0 0 300 400 + + + 265 308 285 380 + Create + ok + 1 + + + 8 10 248 390 + + + 53 0 261 380 + + + 86 46 102 122 + Pane 1 + + + pane + 1 + 2 + + + 53 0 261 380 + + + 100 209 116 285 + Pane 2 + + + pane + 2 + 2 + + + tabs + 1 + 2 + + + contentResID + 0 + tabEnabled + 1 + tabName + Predefined Resource + userPane + + + + contentResID + 0 + tabEnabled + 1 + tabName + Custom Resource + userPane + + + + + + 265 216 285 288 + Cancel + not! + 2 + + + + FALSE + FALSE + FALSE + FALSE + FALSE + 11 + 0 + + + + + + + + + + + + 175 136 475 536 + About ResKnife + + 0 0 300 400 + + + 277 9 293 171 + © 2001 Nicholas Shanks + + + 277 304 293 393 + version 0.3d4 + + + 14 373 30 389 + + + 24 176 110 194 + 50 + + + 33 213 53 299 + TRUE + 5 + 1 + 2 + 1 + 5 + + + 24 341 48 357 + 3 + 1 + 5 + + + 69 364 83 377 + Triangle + 1 + + + 156 18 161 384 + + + 9 144 142 149 + + + 93 17 134 139 + + + Blue Kipper + + + Red Herring + + + + + 25 16 80 140 + Correctly choose from the following to win a prize: + + + 193 350 225 382 + 0 + 0 + + + 110 222 134 321 + 2 + + + 172 28 202 114 + What does this do? + 2 + + + 179 264 240 326 + Bevel + 2 + 4 + -2 + 5 + + BevelMenu + + + Item1 + + + Item2 + + + Item3 + + + + + + 177 181 214 240 + Chicken + 256 + 0 + TRUE + + + 239 197 262 221 + + + 172 145 263 160 + TRUE + 8 + 1400 + + + 73 220 97 302 + 1 + FALSE + + + 225 65 250 90 + 1 + + + 69 346 83 359 + Triangle + FALSE + 2 + + + 259 249 269 377 + 4 + 10 + + + 112 341 132 381 + ! + 1 + + + + FALSE + FALSE + FALSE + FALSE + 6 + 0 + + + + + + + + + + + + + + + + + + + + + + + + + 84 336 384 736 + Preferences + + 0 0 300 400 + + + 10 20 250 380 + Data Protection + + + 40 39 58 371 + data + Warn when deleting resources + warn + + + 265 308 285 380 + OK + ok + 1 + + + 265 216 285 288 + Cancel + not! + 2 + + + 265 20 285 92 + Revert + rvrt + + + 68 39 86 158 + data + 1 + Autosave every + auto + + + 68 166 83 184 + time + FALSE + 5 + + + 70 196 88 372 + minutes + + + + FALSE + FALSE + FALSE + FALSE + 6 + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 96 300 326 534 + Inspector + + 0 0 230 234 + + + 59 16 214 220 + + + 80 22 204 214 + 6 + + + Changed + + + Radio2 + + + Radio + + + Radio + + + Radio + + + SysHeap + + + + + Box + + + 10 12 54 56 + + + 10 67 45 219 + The title of the resource goes here + + + 49 67 65 106 + Type + + + 50 200 66 222 + ID + + + + FALSE + FALSE + FALSE + 5 + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + About Box + + File Window + + Files Owner + + IBCarbonBevelButton + + IBCarbonButton + + IBCarbonButton1 + + IBCarbonButton2 + + IBCarbonChasingArrows + + IBCarbonCheckBox + + IBCarbonCheckBox1 + + IBCarbonClockDate + + IBCarbonClockDate1 + + IBCarbonEditText + + IBCarbonGroupBox + + IBCarbonIcon + + IBCarbonImageWell + + IBCarbonLittleArrows + + IBCarbonMenu + + IBCarbonMenuItem + + IBCarbonMenuItem1 + + IBCarbonMenuItem2 + + IBCarbonMenuItem3 + + IBCarbonMenuItem4 + + IBCarbonMenuItem5 + + IBCarbonMenuItem6 + + IBCarbonMenuItem7 + + IBCarbonMenuItem8 + + IBCarbonRadioGroup + + IBCarbonRootControl + + IBCarbonScrollBar + + IBCarbonSeparator + + IBCarbonSlider + + IBCarbonSlider1 + + IBCarbonStaticText + + IBCarbonStaticText1 + + IBCarbonStaticText2 + + IBCarbonTriangle + + IBCarbonTriangle1 + + IBCarbonUserPane + + IBCarbonUserPane1 + + Inspector + + Menubar + + NSCustomView + + NSCustomView1 + + NSCustomView11 + + NSCustomView2 + + NSCustomView3 + + New Resource + + Preferences + + + 305 + diff --git a/Carbon/Resources/ResKnife.r b/Carbon/Resources/ResKnife.r new file mode 100644 index 0000000..8fa5b13 --- /dev/null +++ b/Carbon/Resources/ResKnife.r @@ -0,0 +1 @@ +#include "Types.r" /*** APPLE MENU ***/ /*resource 'MENU' (128) { 128, textMenuProc, 0xFFFFFFFF, enabled, apple, { "About ResKnifeÉ", noIcon, "A", noMark, plain, "PlugÑins", noIcon, 135, noMark, plain, seperator } };*/ resource 'xmnu' (128, purgeable) { versionZero { { dataItem { 'abou', kMenuNoModifiers, currScript, 0, 0, noHierID, sysFont, naturalGlyph }, dataItem { 'plug', kMenuNoModifiers, currScript, 0, 0, 135, sysFont, naturalGlyph } } }; }; /*** FILE MENU ***/ resource 'MENU' (129) { 129, textMenuProc, 0xFFFFFFFF, enabled, "File", { "New File", noIcon, "N", noMark, plain, "Open FileÉ", noIcon, "O", noMark, plain, "Close Window", noIcon, "W", noMark, plain, "Close File", noIcon, "W", noMark, plain, "Save File", noIcon, "S", noMark, plain, "Save File AsÉ", noIcon, "S", noMark, plain, "RevertÉ", noIcon, noKey, noMark, plain, "-", noIcon, noKey, noMark, plain, "Page SetupÉ", noIcon, "P", noMark, plain, "Print", noIcon, "P", noMark, plain, "-", noIcon, noKey, noMark, plain, "Quit", noIcon, "Q", noMark, plain } }; resource 'xmnu' (129, purgeable) { versionZero { { dataItem { 'new ', kMenuNoModifiers, currScript, 0, 0, noHierID, sysFont, naturalGlyph }, dataItem { 'open', kMenuNoModifiers, currScript, 0, 0, noHierID, sysFont, naturalGlyph }, dataItem { 'clos', kMenuNoModifiers, currScript, 0, 0, noHierID, sysFont, naturalGlyph }, dataItem { 'clsf', kMenuShiftModifier, currScript, 0, 0, noHierID, sysFont, naturalGlyph }, dataItem { 'save', kMenuNoModifiers, currScript, 0, 0, noHierID, sysFont, naturalGlyph }, dataItem { 'svas', kMenuShiftModifier, currScript, 0, 0, noHierID, sysFont, naturalGlyph }, dataItem { 'rvtf', kMenuNoModifiers, currScript, 0, 0, noHierID, sysFont, naturalGlyph }, skipItem {}, dataItem { 'setu', kMenuShiftModifier, currScript, 0, 0, noHierID, sysFont, naturalGlyph }, dataItem { 'prin', kMenuNoModifiers, currScript, 0, 0, noHierID, sysFont, naturalGlyph }, skipItem {}, dataItem { 'quit', kMenuNoModifiers, currScript, 0, 0, noHierID, sysFont, naturalGlyph } } }; }; resource 'xmnu' (130, purgeable) { versionZero { { dataItem { 'undo', kMenuNoModifiers, currScript, 0, 0, noHierID, sysFont, naturalGlyph }, dataItem { 'redo', kMenuShiftModifier, currScript, 0, 0, noHierID, sysFont, naturalGlyph }, skipItem {}, dataItem { 'cut ', kMenuNoModifiers, currScript, 0, 0, noHierID, sysFont, naturalGlyph }, dataItem { 'copy', kMenuNoModifiers, currScript, 0, 0, noHierID, sysFont, naturalGlyph }, dataItem { 'past', kMenuNoModifiers, currScript, 0, 0, noHierID, sysFont, naturalGlyph }, dataItem { 'clea', kMenuNoModifiers, currScript, 0, 0, noHierID, sysFont, naturalGlyph }, dataItem { 'sall', kMenuNoModifiers, currScript, 0, 0, noHierID, sysFont, naturalGlyph }, skipItem {}, dataItem { 'find', kMenuNoModifiers, currScript, 0, 0, noHierID, sysFont, naturalGlyph }, dataItem { 'agin', kMenuNoModifiers, currScript, 0, 0, noHierID, sysFont, naturalGlyph }, skipItem {}, dataItem { 'pref', kMenuNoModifiers, currScript, 0, 0, noHierID, sysFont, naturalGlyph }, } }; }; resource 'xmnu' (131, purgeable) { versionZero { { dataItem { 'newr', kMenuNoModifiers, currScript, 0, 0, noHierID, sysFont, naturalGlyph }, skipItem {}, dataItem { 'edit', kMenuNoModifiers, currScript, 0, 0, noHierID, sysFont, naturalGlyph }, dataItem { 'tmpl', kMenuShiftModifier, currScript, 0, 0, noHierID, sysFont, naturalGlyph }, dataItem { 'tmp ', kMenuShiftModifier | kMenuOptionModifier, currScript, 0, 0, noHierID, sysFont, naturalGlyph }, dataItem { 'hex ', kMenuOptionModifier, currScript, 0, 0, noHierID, sysFont, naturalGlyph }, skipItem {}, dataItem { 'rvtr', kMenuShiftModifier, currScript, 0, 0, noHierID, sysFont, naturalGlyph }, dataItem { 'play', kMenuNoModifiers, currScript, 0, 0, noHierID, sysFont, naturalGlyph }, } }; }; resource 'xmnu' (135, purgeable) { versionZero { { dataItem { 'orgn', kMenuNoModifiers, currScript, 0, 0, noHierID, sysFont, naturalGlyph }, skipItem {}, dataItem { '!hex', kMenuNoModifiers, currScript, 0, 0, noHierID, sysFont, naturalGlyph }, dataItem { '!tmp', kMenuNoModifiers, currScript, 0, 0, noHierID, sysFont, naturalGlyph }, } }; }; resource 'xmnu' (200, purgeable) { versionZero { { dataItem { 'dbug', kMenuNoModifiers, currScript, 0, 0, noHierID, sysFont, naturalGlyph }, skipItem {}, dataItem { 'surp', kMenuNoModifiers, currScript, 0, 0, noHierID, sysFont, naturalGlyph }, dataItem { 'appl', kMenuNoModifiers, currScript, 0, 0, noHierID, sysFont, naturalGlyph }, dataItem { 'appr', kMenuNoModifiers, currScript, 0, 0, noHierID, sysFont, naturalGlyph }, dataItem { 'nav ', kMenuNoModifiers, currScript, 0, 0, noHierID, sysFont, naturalGlyph }, dataItem { 'shet', kMenuNoModifiers, currScript, 0, 0, noHierID, sysFont, naturalGlyph }, } }; }; /*** APPLE EVENTS ***/ #include "AppleEvents.r" #include "AEObjects.r" #include "AERegistry.r" #include "ASRegistry.r" #include "AEUserTermTypes.r" #define kAEResourceEditorSuite 'rsrc' #define kAEResourceClass 'rsrc' #define kAEResourceName 'name' #define kAEResourceType 'type' #define kAECompactResource 'cpct' resource kAETerminologyExtension ( 0, "AppleScript Dictionary" ) { 1, // major version 0, // minor version english, roman, // should be same as resource ID { "Required Suite", "Terms that every application supports", kAERequiredSuite, 1, // level 1, // version { /* events */ /* 1 */ "run", "Sent to the application when it is opened", kCoreEventClass, kAEOpenApplication, /* reply */ noReply, "", replyOptional, singleItem, notEnumerated, reserved, reserved, reserved, reserved, reserved, reserved, reserved, reserved, reserved, reserved, reserved, reserved, reserved, /* direct parameter */ noParams, "", directParamOptional, singleItem, notEnumerated, changesState, reserved, reserved, reserved, reserved, reserved, reserved, reserved, reserved, reserved, reserved, reserved, reserved, { /* other parameters */ }, /* 2 */ "open", "Open the specified object(s)", kCoreEventClass, kAEOpenDocuments, /* reply */ noReply, "", replyOptional, singleItem, notEnumerated, reserved, reserved, reserved, reserved, reserved, reserved, reserved, reserved, reserved, reserved, reserved, reserved, reserved, /* direct parameter */ typeAlias, "list of objects to open", directParamRequired, listOfItems, notEnumerated, changesState, reserved, reserved, reserved, reserved, reserved, reserved, reserved, reserved, reserved, reserved, reserved, reserved, { /* other parameters */ }, /* 3 */ "print", "Print the specified object(s)", kCoreEventClass, kAEPrintDocuments, /* reply */ noReply, "", replyOptional, singleItem, notEnumerated, reserved, reserved, reserved, reserved, reserved, reserved, reserved, reserved, reserved, reserved, reserved, reserved, reserved, /* direct parameter */ typeAlias, "list of objects to print", directParamRequired, listOfItems, notEnumerated, changesState, reserved, reserved, reserved, reserved, reserved, reserved, reserved, reserved, reserved, reserved, reserved, reserved, { /* other parameters */ }, /* 4 */ "quit", "Quit the application", kCoreEventClass, kAEQuitApplication, /* reply */ noReply, "", replyOptional, singleItem, notEnumerated, reserved, reserved, reserved, reserved, reserved, reserved, reserved, reserved, reserved, reserved, reserved, reserved, reserved, /* direct parameter */ noParams, "", directParamOptional, singleItem, notEnumerated, changesState, reserved, reserved, reserved, reserved, reserved, reserved, reserved, reserved, reserved, reserved, reserved, reserved, { /* other parameters */ } }, { /* classes */ }, { /* comparison operators */ }, { /* enumerations */ }, "Resource Editor Suite", "Terms that modern resource editors support", kAEResourceEditorSuite, 1, // level 1, // version { /* array Events */ /* 1 */ "compact", "Compact the specified resource(s)", kAEResourceClass, kAECompactResource, /* reply */ noReply, "", replyOptional, singleItem, notEnumerated, reserved, reserved, reserved, reserved, reserved, reserved, reserved, reserved, reserved, reserved, reserved, reserved, reserved, /* direct parameter */ typeAlias, "list of resources to compact", directParamRequired, listOfItems, notEnumerated, changesState, reserved, reserved, reserved, reserved, reserved, reserved, reserved, reserved, reserved, reserved, reserved, reserved, { /* other parameters */ }, }, { /* classes */ "resource", kAEResourceClass, "a resource of given type and ID", { /* class properties */ /* a */ "name", kAEResourceName, typeText, "the name of the resource (up to 255 chars in length)", reserved, singleItem, notEnumerated, readWrite, reserved, reserved, reserved, reserved, reserved, reserved, reserved, reserved, noApostrophe, notFeminine, notMasculine, singular, /* b */ "type", kAEResourceType, typeSInt16, "the type of the resource (four chars in length)", reserved, singleItem, notEnumerated, readWrite, reserved, reserved, reserved, reserved, reserved, reserved, reserved, reserved, noApostrophe, notFeminine, notMasculine, singular }, { /* elements */ } }, { /* comparison operators */ }, { /* enumerations */ } } }; \ No newline at end of file diff --git a/Carbon/Resources/ResKnife.rsrc b/Carbon/Resources/ResKnife.rsrc new file mode 100644 index 0000000000000000000000000000000000000000..567c06686a339d65188df4baa728bb237da55ab8 GIT binary patch literal 150438 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

9wh_+}u9*5+*+;BNLKs=L}xY+3gam|DgkwyfW} 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 + +@interface ApplicationDelegate : NSObject +{ +} + +- (IBAction)showInfo:(id)sender; +- (IBAction)showPrefs:(id)sender; +- (IBAction)showCreateResourceSheet:(id)sender; +- (IBAction)openResource:(id)sender; +- (IBAction)playSound:(id)sender; +- (void)initUserDefaults; + +@end diff --git a/Cocoa/Classes/ApplicationDelegate.m b/Cocoa/Classes/ApplicationDelegate.m new file mode 100644 index 0000000..5cdf8fb --- /dev/null +++ b/Cocoa/Classes/ApplicationDelegate.m @@ -0,0 +1,91 @@ +#import "ApplicationDelegate.h" +#import "InfoWindowController.h" +#import "PrefsWindowController.h" +#import "ResourceDataSource.h" +#import "CreateResourceSheetController.h" + +@implementation ApplicationDelegate + +- (id)init +{ + self = [super init]; + [NSApp registerServicesMenuSendTypes:[NSArray arrayWithObject:@"NSString"] returnTypes:[NSArray arrayWithObject:@"NSString"]]; + return self; +} + +- (void)awakeFromNib +{ + // Part of my EvilPlanª to find out how many people use ResKnife and how often! + int launchCount = [[NSUserDefaults standardUserDefaults] integerForKey:@"LaunchCount"]; + [[NSUserDefaults standardUserDefaults] setInteger:launchCount + 1 forKey:@"LaunchCount"]; + + [self initUserDefaults]; +} + +- (IBAction)showInfo:(id)sender +{ + [[InfoWindowController sharedInfoWindowController] showWindow:sender]; +} + +- (IBAction)showPrefs:(id)sender +{ + [[PrefsWindowController sharedPrefsWindowController] showWindow:sender]; +} + +- (IBAction)showCreateResourceSheet:(id)sender +{ + // bug: requires ALL main window's delegates to have 'dataSource' declared, + // would be better to use a resourceDocument variable which could point to self for document windows. + return [[[[[NSApp mainWindow] delegate] dataSource] createResourceSheetController] showCreateResourceSheet:sender]; +} + +- (IBAction)openResource:(id)sender +{ +// [NSBundle loadNibNamed:@"HexWindow" owner:self]; +//- (NSString *)pathForAuxiliaryExecutable:(NSString *)executableName; +// [[NSBundle bundleWithIdentifier:@"com.nickshanks.resknife.hexadecimal"] load]; + [[NSBundle bundleWithPath:[[[NSBundle mainBundle] builtInPlugInsPath] stringByAppendingPathComponent:@"HexEditor.plugin"]] load]; +} + +- (IBAction)playSound:(id)sender +{ +} + +- (void)initUserDefaults +{ + // This should probably be added to NSUserDefaults as a category, + // since its universally useful. It loads a defaults.plist file + // from the app wrapper, and then sets the defaults if they don't + // already exist. + + NSUserDefaults *defaults; + NSDictionary *defaultsPlist; + NSEnumerator *overDefaults; + id eachDefault; + + // this isn't required, but saves us a few method calls + defaults = [NSUserDefaults standardUserDefaults]; + + // load the defaults.plist from the app wrapper. This makes it + // easy to add new defaults just using a text editor instead of + // hard-coding them into the application + defaultsPlist = [NSDictionary dictionaryWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"defaults" ofType:@"plist"]]; + + // enumerate over all the keys in the dictionary + overDefaults = [[defaultsPlist allKeys] objectEnumerator]; + while( eachDefault = [overDefaults nextObject] ) + { + // for each key in the dictionary + // check if there is a value already registered for it + // and if there isn't, then register the value that was in the file + if( ![defaults stringForKey:eachDefault] ) + { + [defaults setObject:[defaultsPlist objectForKey:eachDefault] forKey:eachDefault]; + } + } + + // force the defaults to save to the disk + [defaults synchronize]; +} + +@end diff --git a/Cocoa/Classes/AttributesFormatter.h b/Cocoa/Classes/AttributesFormatter.h new file mode 100644 index 0000000..acdc0ea --- /dev/null +++ b/Cocoa/Classes/AttributesFormatter.h @@ -0,0 +1,13 @@ +#import + +@interface AttributesFormatter : NSFormatter +{ +} + +- (NSString *)stringForObjectValue:(id)obj; +- (NSAttributedString *)attributedStringForObjectValue:(id)obj withDefaultAttributes:(NSDictionary *)attrs; +- (NSString *)editingStringForObjectValue:(id)obj; +- (BOOL)getObjectValue:(id *)obj forString:(NSString *)string errorDescription:(NSString **)error; +- (BOOL)isPartialStringValid:(NSString *)partialString newEditingString:(NSString **)newString errorDescription:(NSString **)error; + +@end diff --git a/Cocoa/Classes/AttributesFormatter.m b/Cocoa/Classes/AttributesFormatter.m new file mode 100644 index 0000000..56f2331 --- /dev/null +++ b/Cocoa/Classes/AttributesFormatter.m @@ -0,0 +1,65 @@ +#import "AttributesFormatter.h" +#import + +@implementation AttributesFormatter + +- (NSString *)stringForObjectValue:(id)obj +{ + BOOL addComma = NO; + short attributes = [obj shortValue]; + NSMutableString *string = [NSMutableString string]; + if( attributes & resPreload ) + { + if( addComma ) [string appendString:@", "]; + [string appendString:@"Preload"]; + addComma = YES; + } + if( attributes & resProtected ) + { + if( addComma ) [string appendString:@", "]; + [string appendString:@"Protected"]; + addComma = YES; + } + if( attributes & resLocked ) + { + if( addComma ) [string appendString:@", "]; + [string appendString:@"Locked"]; + addComma = YES; + } + if( attributes & resPurgeable ) + { + if( addComma ) [string appendString:@", "]; + [string appendString:@"Purgeable"]; + addComma = YES; + } + if( attributes & resSysHeap ) + { + if( addComma ) [string appendString:@", "]; + [string appendString:@"SysHeap"]; + addComma = YES; + } + return string; +} + +- (NSAttributedString *)attributedStringForObjectValue:(id)obj withDefaultAttributes:(NSDictionary *)attrs +{ + NSString *string = [self stringForObjectValue:obj]; + return [[NSAttributedString alloc] initWithString:string attributes:attrs]; +} + +- (NSString *)editingStringForObjectValue:(id)obj +{ + return nil; +} + +- (BOOL)getObjectValue:(id *)obj forString:(NSString *)string errorDescription:(NSString **)error +{ + return NO; +} + +- (BOOL)isPartialStringValid:(NSString *)partialString newEditingString:(NSString **)newString errorDescription:(NSString **)error +{ + return NO; +} + +@end diff --git a/Cocoa/Classes/CreateResourceSheetController.h b/Cocoa/Classes/CreateResourceSheetController.h new file mode 100644 index 0000000..a93db87 --- /dev/null +++ b/Cocoa/Classes/CreateResourceSheetController.h @@ -0,0 +1,21 @@ +#import +#import "ResourceDataSource.h" + +@interface CreateResourceSheetController : NSWindowController +{ + IBOutlet ResourceDataSource *dataSource; + IBOutlet NSMatrix *attributesMatrix; + IBOutlet NSButton *cancelButton; + IBOutlet NSButton *createButton; + IBOutlet NSTextField *nameView; + IBOutlet NSTextField *resIDView; + IBOutlet NSTextField *typeView; + IBOutlet NSPopUpButton *typePopup; + IBOutlet NSWindow *parent; +} + +- (IBAction)showCreateResourceSheet:(id)sender; +- (IBAction)hideCreateResourceSheet:(id)sender; +- (IBAction)typePopupSelection:(id)sender; + +@end \ No newline at end of file diff --git a/Cocoa/Classes/CreateResourceSheetController.m b/Cocoa/Classes/CreateResourceSheetController.m new file mode 100644 index 0000000..fb03785 --- /dev/null +++ b/Cocoa/Classes/CreateResourceSheetController.m @@ -0,0 +1,32 @@ +#import "CreateResourceSheetController.h" +#import + +@implementation CreateResourceSheetController + +- (IBAction)showCreateResourceSheet:(id)sender +{ + [NSApp beginSheet:[self window] modalForWindow:parent modalDelegate:self didEndSelector:NULL contextInfo:nil]; +} + +- (IBAction)hideCreateResourceSheet:(id)sender +{ + if( sender == createButton ) + { + unsigned short attributes = 0; + attributes ^= [[attributesMatrix cellAtRow:0 column:0] intValue]? resPreload:0; + attributes ^= [[attributesMatrix cellAtRow:1 column:0] intValue]? resPurgeable:0; + attributes ^= [[attributesMatrix cellAtRow:2 column:0] intValue]? resLocked:0; + attributes ^= [[attributesMatrix cellAtRow:0 column:1] intValue]? resSysHeap:0; + attributes ^= [[attributesMatrix cellAtRow:1 column:1] intValue]? resProtected:0; + [dataSource addResource:[Resource resourceOfType:[typeView stringValue] andID:[NSNumber numberWithShort:(short) [resIDView intValue]] withName:[nameView stringValue] andAttributes:[NSNumber numberWithUnsignedShort:attributes]]]; + } + [[self window] orderOut:nil]; + [NSApp endSheet:[self window]]; +} + +- (IBAction)typePopupSelection:(id)sender +{ + +} + +@end diff --git a/Cocoa/Classes/InfoWindow.h b/Cocoa/Classes/InfoWindow.h new file mode 100644 index 0000000..5599309 --- /dev/null +++ b/Cocoa/Classes/InfoWindow.h @@ -0,0 +1,6 @@ +#import + +@interface InfoWindow : NSPanel +{ +} +@end diff --git a/Cocoa/Classes/InfoWindow.m b/Cocoa/Classes/InfoWindow.m new file mode 100644 index 0000000..934295b --- /dev/null +++ b/Cocoa/Classes/InfoWindow.m @@ -0,0 +1,15 @@ +#import "InfoWindow.h" + +@implementation InfoWindow + +- (BOOL)canBecomeKeyWindow +{ + return NO; +} + +- (BOOL)canBecomeMainWindow +{ + return NO; +} + +@end diff --git a/Cocoa/Classes/InfoWindowController.h b/Cocoa/Classes/InfoWindowController.h new file mode 100644 index 0000000..09b41f6 --- /dev/null +++ b/Cocoa/Classes/InfoWindowController.h @@ -0,0 +1,34 @@ +#import + +@class ResourceDocument, Resource; + +enum Attributes +{ + changedBox = 0, + preloadBox, + protectedBox, + lockedBox, + purgableBox, + systemHeapBox +}; + +@interface InfoWindowController : NSWindowController +{ + IBOutlet NSMatrix *attributesMatrix; + IBOutlet NSImageView *iconView; + IBOutlet NSTextField *nameView; + IBOutlet NSTextField *resIDView; + IBOutlet NSTextField *typeView; + +@private + ResourceDocument *currentDocument; + Resource *selectedResource; +} + +- (void)updateInfoWindow; +- (void)setMainWindow:(NSWindow *)mainWindow; +- (IBAction)attributesChanged:(id)sender; + ++ (id)sharedInfoWindowController; + +@end diff --git a/Cocoa/Classes/InfoWindowController.m b/Cocoa/Classes/InfoWindowController.m new file mode 100644 index 0000000..f232a2f --- /dev/null +++ b/Cocoa/Classes/InfoWindowController.m @@ -0,0 +1,102 @@ +#import "InfoWindowController.h" +#import // Actually I only need CarbonCore.framework, but and don't work, so I don't know what else to do +#import "ResourceDocument.h" +#import "Resource.h" + +@implementation InfoWindowController + +- (id)init +{ + self = [self initWithWindowNibName:@"InfoWindow"]; + if( self ) [self setWindowFrameAutosaveName:@"Resource Info"]; + return self; +} + +- (void)windowDidLoad +{ + [super windowDidLoad]; + + [self setMainWindow:[NSApp mainWindow]]; + [self updateInfoWindow]; + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(mainWindowChanged:) name:NSWindowDidBecomeMainNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(selectedResourceChanged:) name:NSOutlineViewSelectionDidChangeNotification object:nil]; +} + +- (void)dealloc +{ + [[NSNotificationCenter defaultCenter] removeObserver:self]; + [super dealloc]; +} + +- (void)updateInfoWindow +{ + if( selectedResource ) + { + [nameView setStringValue:[selectedResource name]]; + [typeView setStringValue:[selectedResource type]]; + [resIDView setStringValue:[[selectedResource resID] stringValue]]; + [[attributesMatrix cellAtRow:changedBox column:0] setState:[[selectedResource attributes] shortValue] & resChanged]; + [[attributesMatrix cellAtRow:preloadBox column:0] setState:[[selectedResource attributes] shortValue] & resPreload]; + [[attributesMatrix cellAtRow:protectedBox column:0] setState:[[selectedResource attributes] shortValue] & resProtected]; + [[attributesMatrix cellAtRow:lockedBox column:0] setState:[[selectedResource attributes] shortValue] & resLocked]; + [[attributesMatrix cellAtRow:purgableBox column:0] setState:[[selectedResource attributes] shortValue] & resPurgeable]; + [[attributesMatrix cellAtRow:systemHeapBox column:0] setState:[[selectedResource attributes] shortValue] & resSysHeap]; + } + else + { + [nameView setStringValue:@""]; + [typeView setStringValue:@""]; + [resIDView setStringValue:@""]; + [[attributesMatrix cellAtRow:changedBox column:0] setState:NSOffState]; + [[attributesMatrix cellAtRow:preloadBox column:0] setState:NSOffState]; + [[attributesMatrix cellAtRow:protectedBox column:0] setState:NSOffState]; + [[attributesMatrix cellAtRow:lockedBox column:0] setState:NSOffState]; + [[attributesMatrix cellAtRow:purgableBox column:0] setState:NSOffState]; + [[attributesMatrix cellAtRow:systemHeapBox column:0] setState:NSOffState]; + } +} + +- (void)setMainWindow:(NSWindow *)mainWindow +{ + NSWindowController *controller = [mainWindow windowController]; + + if( [[controller document] isKindOfClass:[ResourceDocument class]] ) + currentDocument = [controller document]; + else currentDocument = nil; + + selectedResource = [[currentDocument outlineView] itemAtRow:[[currentDocument outlineView] selectedRow]]; + [self updateInfoWindow]; +} + +- (void)mainWindowChanged:(NSNotification *)notification +{ + [self setMainWindow:[notification object]]; +} + +- (void)selectedResourceChanged:(NSNotification *)notification +{ + selectedResource = [[notification object] itemAtRow:[[notification object] selectedRow]]; + [self updateInfoWindow]; +} + +- (IBAction)attributesChanged:(id)sender +{ + short attr = 0x0001 << [sender selectedRow]+1; + short number = ([[selectedResource attributes] shortValue] ^ attr); + [selectedResource setAttributes:[NSNumber numberWithShort:number]]; + [self updateInfoWindow]; +} + ++ (id)sharedInfoWindowController +{ + static InfoWindowController *sharedInfoWindowController = nil; + + if( !sharedInfoWindowController ) + { + sharedInfoWindowController = [[InfoWindowController allocWithZone:[self zone]] init]; + } + return sharedInfoWindowController; +} + +@end diff --git a/Cocoa/Classes/NameFormatter.h b/Cocoa/Classes/NameFormatter.h new file mode 100644 index 0000000..d1fbc44 --- /dev/null +++ b/Cocoa/Classes/NameFormatter.h @@ -0,0 +1,13 @@ +#import + +@interface NameFormatter : NSFormatter +{ +} + +- (NSString *)stringForObjectValue:(id)obj; +- (NSAttributedString *)attributedStringForObjectValue:(id)obj withDefaultAttributes:(NSDictionary *)attrs; +- (NSString *)editingStringForObjectValue:(id)obj; +- (BOOL)getObjectValue:(id *)obj forString:(NSString *)string errorDescription:(NSString **)error; +- (BOOL)isPartialStringValid:(NSString *)partialString newEditingString:(NSString **)newString errorDescription:(NSString **)error; + +@end diff --git a/Cocoa/Classes/NameFormatter.m b/Cocoa/Classes/NameFormatter.m new file mode 100644 index 0000000..689d590 --- /dev/null +++ b/Cocoa/Classes/NameFormatter.m @@ -0,0 +1,41 @@ +#import "NameFormatter.h" +#import + +@implementation NameFormatter + +- (NSString *)stringForObjectValue:(id)obj +{ + if( ![obj isKindOfClass:[NSString class]] ) return nil; + if( [obj isEqualToString:@""] ) + { + if( NO ) return NSLocalizedString( @"Custom Icon", nil ); + else return NSLocalizedString( @"Untitled Resource", nil ); + } + else return obj; +} + +- (NSAttributedString *)attributedStringForObjectValue:(id)obj withDefaultAttributes:(NSDictionary *)attrs +{ + NSString *string = [self stringForObjectValue:obj]; + if( [obj isEqualToString:@""] ) + return [[NSAttributedString alloc] initWithString:string attributes:[NSDictionary dictionaryWithObject:[NSColor grayColor] forKey:@"NSColor"]]; + else return [[NSAttributedString alloc] initWithString:string attributes:attrs]; +} + +- (NSString *)editingStringForObjectValue:(id)obj +{ + return obj; +} + +- (BOOL)getObjectValue:(id *)obj forString:(NSString *)string errorDescription:(NSString **)error +{ + *obj = string; + return YES; +} + +- (BOOL)isPartialStringValid:(NSString *)partialString newEditingString:(NSString **)newString errorDescription:(NSString **)error +{ + return YES; +} + +@end diff --git a/Cocoa/Classes/OutlineViewDelegate.h b/Cocoa/Classes/OutlineViewDelegate.h new file mode 100644 index 0000000..9611e39 --- /dev/null +++ b/Cocoa/Classes/OutlineViewDelegate.h @@ -0,0 +1,13 @@ +#import +#import "NameFormatter.h" +#import "SizeFormatter.h" +#import "AttributesFormatter.h" + +@interface OutlineViewDelegate : NSObject +{ + IBOutlet NSWindow *window; + IBOutlet NameFormatter *nameFormatter; + IBOutlet SizeFormatter *sizeFormatter; + IBOutlet AttributesFormatter *attributesFormatter; +} +@end diff --git a/Cocoa/Classes/OutlineViewDelegate.m b/Cocoa/Classes/OutlineViewDelegate.m new file mode 100644 index 0000000..b776fe5 --- /dev/null +++ b/Cocoa/Classes/OutlineViewDelegate.m @@ -0,0 +1,20 @@ +#import "OutlineViewDelegate.h" + +@implementation OutlineViewDelegate + +- (void)outlineView:(NSOutlineView *)outlineView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item +{ + NSTableColumn *nameColumn = [outlineView tableColumnWithIdentifier:@"name"]; + NSTableColumn *sizeColumn = [outlineView tableColumnWithIdentifier:@"size"]; + NSTableColumn *attributesColumn = [outlineView tableColumnWithIdentifier:@"attributes"]; + if( [tableColumn isEqual:nameColumn] ) [cell setFormatter:nameFormatter]; + else if( [tableColumn isEqual:sizeColumn] ) [cell setFormatter:sizeFormatter]; + else if( [tableColumn isEqual:attributesColumn] ) [cell setFormatter:attributesFormatter]; +} + +- (BOOL)outlineView:(NSOutlineView *)outlineView shouldEditTableColumn:(NSTableColumn *)tableColumn item:(id)item +{ + return YES; +} + +@end diff --git a/Cocoa/Classes/PrefsWindowController.h b/Cocoa/Classes/PrefsWindowController.h new file mode 100644 index 0000000..f601cdc --- /dev/null +++ b/Cocoa/Classes/PrefsWindowController.h @@ -0,0 +1,22 @@ +#import + +enum DataProtection +{ + preserveBackupsBox = 0, + autosaveBox, + deleteResourceWarningBox +}; + +@interface PrefsWindowController : NSWindowController +{ + IBOutlet NSTextField *autosaveIntervalField; + IBOutlet NSMatrix *dataProtectionMatrix; +} + +- (IBAction)acceptPrefs:(id)sender; +- (IBAction)cancelPrefs:(id)sender; +- (IBAction)resetToDefault:(id)sender; + ++ (id)sharedPrefsWindowController; + +@end \ No newline at end of file diff --git a/Cocoa/Classes/PrefsWindowController.m b/Cocoa/Classes/PrefsWindowController.m new file mode 100644 index 0000000..49d36f2 --- /dev/null +++ b/Cocoa/Classes/PrefsWindowController.m @@ -0,0 +1,93 @@ +#import "PrefsWindowController.h" + +@implementation PrefsWindowController + +- (id)init +{ + self = [self initWithWindowNibName:@"PrefsWindow"]; + if( self ) [self setWindowFrameAutosaveName:@"ResKnife Preferences"]; + return self; +} + +- (void)awakeFromNib +{ + // load preferencesÉ + NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; + BOOL preserveBackups = [defaults boolForKey:@"PreserveBackups"]; + BOOL autosave = [defaults boolForKey:@"Autosave"]; + int autosaveInterval = [defaults integerForKey:@"AutosaveInterval"]; + BOOL deleteResourceWarning = [defaults boolForKey:@"DeleteResourceWarning"]; + + // Éand set widgets accordingly + [[dataProtectionMatrix cellAtRow:preserveBackupsBox column:0] setState:preserveBackups]; + [[dataProtectionMatrix cellAtRow:autosaveBox column:0] setState:autosave]; + [autosaveIntervalField setStringValue:[NSString stringWithFormat:@"%d", autosaveInterval]]; + [[dataProtectionMatrix cellAtRow:deleteResourceWarningBox column:0] setState:deleteResourceWarning]; +} + +- (IBAction)acceptPrefs:(id)sender +{ + NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; + BOOL preserveBackups = [[dataProtectionMatrix cellAtRow:preserveBackupsBox column:0] intValue]? YES:NO; + BOOL autosave = [[dataProtectionMatrix cellAtRow:autosaveBox column:0] intValue]? YES:NO; + int autosaveInterval = [autosaveIntervalField intValue]; + BOOL deleteResourceWarning = [[dataProtectionMatrix cellAtRow:deleteResourceWarningBox column:0] intValue]? YES:NO; + + // hide the window + [[self window] orderOut:nil]; + + // now save the data to the defaults file + [defaults setBool:preserveBackups forKey:@"PreserveBackups"]; // bug: this put 1 or 0 into the defaults file rather than YES or NO + [defaults setBool:autosave forKey:@"Autosave"]; + [defaults setInteger:autosaveInterval forKey:@"AutosaveInterval"]; + [defaults setBool:deleteResourceWarning forKey:@"DeleteResourceWarning"]; + [defaults synchronize]; +} + +- (IBAction)cancelPrefs:(id)sender +{ + // load saved defaults + NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; + BOOL preserveBackups = [defaults boolForKey:@"PreserveBackups"]; + BOOL autosave = [defaults boolForKey:@"Autosave"]; + int autosaveInterval = [defaults integerForKey:@"AutosaveInterval"]; + BOOL deleteResourceWarning = [defaults boolForKey:@"DeleteResourceWarning"]; + + // hide the window + [[self window] orderOut:nil]; + + // and reset dialog to match + [[dataProtectionMatrix cellAtRow:preserveBackupsBox column:0] setState:preserveBackups]; + [[dataProtectionMatrix cellAtRow:autosaveBox column:0] setState:autosave]; + [autosaveIntervalField setStringValue:[NSString stringWithFormat:@"%d", autosaveInterval]]; + [[dataProtectionMatrix cellAtRow:deleteResourceWarningBox column:0] setState:deleteResourceWarning]; +} + +- (IBAction)resetToDefault:(id)sender +{ + // reset prefs window widgets to values stored in defaults.plist file + NSDictionary *defaultsPlist = [NSDictionary dictionaryWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"defaults" ofType:@"plist"]]; + BOOL preserveBackups = [[defaultsPlist objectForKey:@"PreserveBackups"] intValue]? YES:NO; // bug: this always evaluates to NO, even if the object in the dictionary is YES + BOOL autosave = [[defaultsPlist objectForKey:@"Autosave"] intValue]? YES:NO; + int autosaveInterval = [[defaultsPlist objectForKey:@"AutosaveInterval"] intValue]; + BOOL deleteResourceWarning = [[defaultsPlist objectForKey:@"DeleteResourceWarning"] intValue]? YES:NO; + + // note that this function does notmodify the user defaults - the user still has to accept or cancel the panel + [[dataProtectionMatrix cellAtRow:preserveBackupsBox column:0] setState:preserveBackups]; + [[dataProtectionMatrix cellAtRow:autosaveBox column:0] setState:autosave]; + [autosaveIntervalField setStringValue:[NSString stringWithFormat:@"%d", autosaveInterval]]; + [[dataProtectionMatrix cellAtRow:deleteResourceWarningBox column:0] setState:deleteResourceWarning]; +} + ++ (id)sharedPrefsWindowController +{ + static PrefsWindowController *sharedPrefsWindowController = nil; + + if( !sharedPrefsWindowController ) + { + sharedPrefsWindowController = [[PrefsWindowController allocWithZone:[self zone]] init]; + } + return sharedPrefsWindowController; +} + +@end diff --git a/Cocoa/Classes/Resource.h b/Cocoa/Classes/Resource.h new file mode 100644 index 0000000..0ee16a1 --- /dev/null +++ b/Cocoa/Classes/Resource.h @@ -0,0 +1,44 @@ +#import + +extern NSString *ResourceChangedNotification; + +@interface Resource : NSObject +{ + // resource information + NSString *name; + NSString *type; + NSNumber *resID; // signed short + NSNumber *size; // unsigned long + NSNumber *attributes; // unsigned short + + // flags + BOOL dirty; + + // the actual data + NSData *data; +} + +- (NSString *)name; +- (void)setName:(NSString *)newName; +- (NSString *)type; +- (void)setType:(NSString *)newType; +- (NSNumber *)resID; +- (void)setResID:(NSNumber *)newResID; +- (NSNumber *)size; +- (void)setSize:(NSNumber *)newSize; +- (NSNumber *)attributes; +- (void)setAttributes:(NSNumber *)newAttributes; +- (BOOL)dirty; +- (void)setDirty:(BOOL)newValue; +- (NSData *)data; +- (void)setData:(NSData *)newData; + +- (id)initWithType:(NSString *)typeValue andID:(NSNumber *)resIDValue; +- (id)initWithType:(NSString *)typeValue andID:(NSNumber *)resIDValue withName:(NSString *)nameValue andAttributes:(NSNumber *)attributesValue; +- (id)initWithType:(NSString *)typeValue andID:(NSNumber *)resIDValue withName:(NSString *)nameValue andAttributes:(NSNumber *)attributesValue data:(NSData *)dataValue ofLength:(NSNumber *)sizeValue; + ++ (id)resourceOfType:(NSString *)typeValue andID:(NSNumber *)resIDValue; ++ (id)resourceOfType:(NSString *)typeValue andID:(NSNumber *)resIDValue withName:(NSString *)nameValue andAttributes:(NSNumber *)attributesValue; ++ (id)resourceOfType:(NSString *)typeValue andID:(NSNumber *)resIDValue withName:(NSString *)nameValue andAttributes:(NSNumber *)attributesValue data:(NSData *)dataValue ofLength:(NSNumber *)sizeValue; + +@end diff --git a/Cocoa/Classes/Resource.m b/Cocoa/Classes/Resource.m new file mode 100644 index 0000000..dea1554 --- /dev/null +++ b/Cocoa/Classes/Resource.m @@ -0,0 +1,155 @@ +#import "Resource.h" + +@implementation Resource + +NSString *ResourceChangedNotification = @"ResourceChangedNotification"; + +- (id)init +{ + self = [super init]; + [self initWithType:@"" andID:[NSNumber numberWithShort:128]]; + return self; +} + +- (void)dealloc +{ + [name release]; + [type release]; + [resID release]; + [size release]; + [attributes release]; + [data release]; + [super dealloc]; +} + +- (NSString *)name +{ + return name; +} + +- (void)setName:(NSString *)newName +{ + [name autorelease]; + name = [newName copy]; + [[NSNotificationCenter defaultCenter] postNotificationName:ResourceChangedNotification object:self]; +// NSLog( @"%@ posted beacause name changed to %@", ResourceChangedNotification, name ); +} + +- (NSString *)type +{ + return type; +} + +- (void)setType:(NSString *)newType +{ + [type autorelease]; + type = [newType copy]; + [[NSNotificationCenter defaultCenter] postNotificationName:ResourceChangedNotification object:self]; +// NSLog( @"%@ posted beacause type changed to %@", ResourceChangedNotification, type ); +} + +- (NSNumber *)resID +{ + return resID; +} + +- (void)setResID:(NSNumber *)newResID +{ + [resID autorelease]; + resID = [newResID copy]; + [[NSNotificationCenter defaultCenter] postNotificationName:ResourceChangedNotification object:self]; +// NSLog( @"%@ posted beacause res ID changed to %@", ResourceChangedNotification, [resID stringValue] ); +} + +- (NSNumber *)size +{ + return size; +} + +- (void)setSize:(NSNumber *)newSize +{ + [size autorelease]; + size = [newSize copy]; + [[NSNotificationCenter defaultCenter] postNotificationName:ResourceChangedNotification object:self]; +// NSLog( @"%@ posted beacause size changed to %@", ResourceChangedNotification, [size stringValue] ); +} + +- (NSNumber *)attributes +{ + return attributes; +} + +- (void)setAttributes:(NSNumber *)newAttributes +{ + [attributes autorelease]; + attributes = [newAttributes copy]; + [[NSNotificationCenter defaultCenter] postNotificationName:ResourceChangedNotification object:self]; +// NSLog( @"%@ posted beacause attributes changed to %@", ResourceChangedNotification, [attributes stringValue] ); +} + +- (BOOL)dirty +{ + return dirty; +} + +- (void)setDirty:(BOOL)newValue +{ + dirty = newValue; + [[NSNotificationCenter defaultCenter] postNotificationName:ResourceChangedNotification object:self]; +// NSLog( @"%@ posted beacause resource became %@", ResourceChangedNotification, dirty? @"dirty":@"clean" ); +} + +- (NSData *)data +{ + return data; +} + +- (void)setData:(NSData *)newData +{ + [data autorelease]; + data = [newData retain]; + [[NSNotificationCenter defaultCenter] postNotificationName:ResourceChangedNotification object:self]; +// NSLog( @"%@ posted beacause data changed", ResourceChangedNotification ); +} + +- (id)initWithType:(NSString *)typeValue andID:(NSNumber *)resIDValue +{ + [self initWithType:typeValue andID:resIDValue withName:@"" andAttributes:[NSNumber numberWithUnsignedShort:0]]; + return self; +} + +- (id)initWithType:(NSString *)typeValue andID:(NSNumber *)resIDValue withName:(NSString *)nameValue andAttributes:(NSNumber *)attributesValue +{ + [self initWithType:typeValue andID:resIDValue withName:nameValue andAttributes:attributesValue data:[NSData data] ofLength:[NSNumber numberWithUnsignedLong:0]]; + return self; +} + +- (id)initWithType:(NSString *)typeValue andID:(NSNumber *)resIDValue withName:(NSString *)nameValue andAttributes:(NSNumber *)attributesValue data:(NSData *)dataValue ofLength:(NSNumber *)sizeValue +{ + [super init]; + [self setName:nameValue]; + [self setType:typeValue]; + [self setResID:resIDValue]; + [self setSize:sizeValue]; + [self setAttributes:attributesValue]; + [self setData:dataValue]; + return self; +} + ++ (id)resourceOfType:(NSString *)typeValue andID:(NSNumber *)resIDValue +{ + return [[Resource allocWithZone:[self zone]] initWithType:typeValue andID:resIDValue]; +} + ++ (id)resourceOfType:(NSString *)typeValue andID:(NSNumber *)resIDValue withName:(NSString *)nameValue andAttributes:(NSNumber *)attributesValue +{ + return [[Resource allocWithZone:[self zone]] initWithType:typeValue andID:resIDValue withName:nameValue andAttributes:attributesValue]; +} + ++ (id)resourceOfType:(NSString *)typeValue andID:(NSNumber *)resIDValue withName:(NSString *)nameValue andAttributes:(NSNumber *)attributesValue data:(NSData *)dataValue ofLength:(NSNumber *)sizeValue +{ + return [[Resource allocWithZone:[self zone]] initWithType:typeValue andID:resIDValue withName:nameValue andAttributes:attributesValue data:dataValue ofLength:sizeValue]; +} + + +@end diff --git a/Cocoa/Classes/ResourceDataSource.h b/Cocoa/Classes/ResourceDataSource.h new file mode 100644 index 0000000..c4da2bd --- /dev/null +++ b/Cocoa/Classes/ResourceDataSource.h @@ -0,0 +1,23 @@ +#import +#import "Resource.h" + +@class CreateResourceSheetController; + +@interface ResourceDataSource : NSObject +{ + IBOutlet NSWindow *window; + IBOutlet NSOutlineView *outlineView; + IBOutlet CreateResourceSheetController *createResourceSheetController; + + NSMutableArray *resources; +} + +- (CreateResourceSheetController *)createResourceSheetController; +- (NSWindow *)window; +- (NSArray *)resources; +- (void)setResources:(NSMutableArray *)newResources; + +- (void)addResource:(Resource *)resource; +- (void)generateTestData; + +@end diff --git a/Cocoa/Classes/ResourceDataSource.m b/Cocoa/Classes/ResourceDataSource.m new file mode 100644 index 0000000..a0f6702 --- /dev/null +++ b/Cocoa/Classes/ResourceDataSource.m @@ -0,0 +1,91 @@ +#import "ResourceDataSource.h" + +@implementation ResourceDataSource + +- (id)init +{ + self = [super init]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(resourceDidChange:) name:ResourceChangedNotification object:nil]; + return self; +} + +- (void)dealloc +{ + [[NSNotificationCenter defaultCenter] removeObserver:self]; + [super dealloc]; +} + +- (CreateResourceSheetController *)createResourceSheetController +{ + return createResourceSheetController; +} + +- (NSWindow *)window +{ + return window; +} + +- (NSArray *)resources +{ + return resources; +} + +- (void)setResources:(NSMutableArray *)newResources +{ + [resources autorelease]; + resources = [newResources retain]; + [outlineView reloadData]; +} + +- (void)addResource:(Resource *)resource +{ + [resources addObject:resource]; + [outlineView reloadData]; +// [outlineView noteNumberOfRowsChanged]; // what is this for if it doesn't update the damn outliine view! +} + +- (void)resourceDidChange:(NSNotification *)notification +{ + [outlineView reloadData]; +} + +- (void)generateTestData +{ + [self addResource:[Resource resourceOfType:@"____" andID:[NSNumber numberWithShort:-1] withName:@"underscore" andAttributes:[NSNumber numberWithUnsignedShort:0x8080]]]; + [self addResource:[Resource resourceOfType:@"ÐÐÐÐ" andID:[NSNumber numberWithShort:0] withName:@"hyphen" andAttributes:[NSNumber numberWithUnsignedShort:0xFFFF] data:[NSData data] ofLength:[NSNumber numberWithUnsignedLong:1023]]]; + [self addResource:[Resource resourceOfType:@"----" andID:[NSNumber numberWithShort:128] withName:@"minus" andAttributes:[NSNumber numberWithUnsignedShort:0xABCD] data:[NSData data] ofLength:[NSNumber numberWithUnsignedLong:12000]]]; + [self addResource:[Resource resourceOfType:@"ÑÑÑÑ" andID:[NSNumber numberWithShort:32000] withName:@"en-dash" andAttributes:[NSNumber numberWithUnsignedShort:0x1234] data:[NSData data] ofLength:[NSNumber numberWithUnsignedLong:4096]]]; + [self addResource:[Resource resourceOfType:@"****" andID:[NSNumber numberWithShort:-32000]]]; +} + +- (id)outlineView:(NSOutlineView *)outlineView child:(int)index ofItem:(id)item +{ + #pragma unused( outlineView, item ) + return [resources objectAtIndex:index]; +} + +- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item +{ + #pragma unused( outlineView, item ) + return NO; +} + +- (int)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item +{ + #pragma unused( outlineView, item ) + return [resources count]; +} + +- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item +{ + #pragma unused( outlineView ) + return [item valueForKey:[tableColumn identifier]]; +} + +- (void)outlineView:(NSOutlineView *)outlineView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn byItem:(id)item +{ + NSString *identifier = [tableColumn identifier]; + [item takeValue:object forKey:identifier]; +} + +@end diff --git a/Cocoa/Classes/ResourceDocument.h b/Cocoa/Classes/ResourceDocument.h new file mode 100644 index 0000000..b1abcda --- /dev/null +++ b/Cocoa/Classes/ResourceDocument.h @@ -0,0 +1,21 @@ +#import +#import // Actually I only need CarbonCore.framework +#import "ResourceDataSource.h" + +@interface ResourceDocument : NSDocument +{ + IBOutlet NSOutlineView *outlineView; + IBOutlet ResourceDataSource *dataSource; + + NSMutableArray *resources; + BOOL saveToDataFork; + HFSUniStr255 *otherFork; // name of fork to save to if not using data fork (usually 'RESOURCE_FORK' as returned from FSGetResourceForkName() -- ignored if saveToDataFork is YES ) +} + +- (BOOL)readResourceMap:(SInt16)fileRefNum; +- (BOOL)writeResourceMap:(SInt16)fileRefNum; + +- (NSOutlineView *)outlineView; +- (ResourceDataSource *)dataSource; + +@end \ No newline at end of file diff --git a/Cocoa/Classes/ResourceDocument.m b/Cocoa/Classes/ResourceDocument.m new file mode 100644 index 0000000..516dc98 --- /dev/null +++ b/Cocoa/Classes/ResourceDocument.m @@ -0,0 +1,256 @@ +#import "ResourceDocument.h" +#import "Resource.h" + +@implementation ResourceDocument + +- (id)init +{ + self = [super init]; + resources = [NSMutableArray array]; + otherFork = nil; + return self; +} + +- (void)dealloc +{ + if( otherFork ) + DisposePtr( (Ptr) otherFork ); + [resources release]; + [super dealloc]; +} + +- (NSString *)windowNibName +{ + // Override returning the nib file name of the document + // If you need to use a subclass of NSWindowController or if your document supports multiple NSWindowControllers, you should remove this method and override -makeWindowControllers instead. + return @"ResourceDocument"; +} + +- (void)windowControllerDidLoadNib:(NSWindowController *) aController +{ + [super windowControllerDidLoadNib:aController]; + // Add any code here that need to be executed once the windowController has loaded the document's window. + [dataSource setResources:resources]; +} + +- (BOOL)keepBackupFile +{ + return NO; // return whatever the user preference is for this! (NSDefaults) +} + +- (BOOL)windowShouldClose:(NSWindow *)sender +{ + NSString *file = [[[sender representedFilename] lastPathComponent] stringByDeletingPathExtension]; + if( [file isEqualToString:@""] ) file = @"this document"; + NSBeginAlertSheet( @"Save Document?", @"Save", @"Cancel", @"DonÕt Save", sender, self, @selector(didEndShouldCloseSheet:returnCode:contextInfo:), NULL, sender, @"Do you wish to save %@?", file ); + return NO; +} + +- (void)didEndShouldCloseSheet:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo +{ + if( returnCode == NSAlertDefaultReturn ) // save then close + { + [self saveDocument:contextInfo]; + [(NSWindow *)contextInfo close]; + } + else if( returnCode == NSAlertOtherReturn ) // don't save, just close + { + [(NSWindow *)contextInfo close]; + } + else if( returnCode == NSAlertErrorReturn ) + { + NSLog( @"didEndShouldCloseSheet received NSAlertErrorReturn return code" ); + } + // else returnCode == NSAlertAlternateReturn, cancel +} + +- (BOOL)readFromFile:(NSString *)fileName ofType:(NSString *)type +{ + BOOL succeeded = NO; + OSStatus error = noErr; + HFSUniStr255 *resourceForkName = (HFSUniStr255 *) NewPtrClear( sizeof(HFSUniStr255) ); + FSRef *fileRef = (FSRef *) NewPtrClear( sizeof(FSRef) ); + SInt16 fileRefNum = 0; + + // open fork with resources in it + error = FSPathMakeRef( [fileName cString], fileRef, nil ); + error = FSGetResourceForkName( resourceForkName ); + SetResLoad( false ); // don't load "preload" resources + error = FSOpenResourceFile( fileRef, resourceForkName->length, (UniChar *) &resourceForkName->unicode, fsRdPerm, &fileRefNum); + if( error ) // try to open data fork instead + error = FSOpenResourceFile( fileRef, 0, nil, fsRdPerm, &fileRefNum); + else otherFork = resourceForkName; + SetResLoad( true ); // restore resource loading as soon as is possible + + // read the resources + if( fileRefNum && !error ) + succeeded = [self readResourceMap:fileRefNum]; + + // tidy up loose ends + if( !otherFork ) DisposePtr( (Ptr) resourceForkName ); // only delete if we're not saving it + if( fileRefNum ) FSClose( fileRefNum ); + DisposePtr( (Ptr) fileRef ); + return succeeded; +} + +- (BOOL)readFromURL:(NSURL *)url ofType:(NSString *)type +{ + return NO; +} + +- (BOOL)readResourceMap:(SInt16)fileRefNum +{ + OSStatus error = noErr; + unsigned short i, j, n; + SInt16 oldResFile = CurResFile(); + UseResFile( fileRefNum ); + + for( i = 1; i <= Count1Types(); i++ ) + { + ResType resType; + Get1IndType( &resType, i ); + n = Count1Resources( resType ); + for( j = 1; j <= n; j++ ) + { + Str255 nameStr; + long sizeLong; + short resIDShort; + short attrsShort; + Handle resourceHandle; + + resourceHandle = Get1IndResource( resType, j ); + error = ResError(); + if( error != noErr ) + { + UseResFile( oldResFile ); + return NO; + } + + GetResInfo( resourceHandle, &resIDShort, &resType, nameStr ); + sizeLong = GetResourceSizeOnDisk( resourceHandle ); + attrsShort = GetResAttrs( resourceHandle ); + HLockHi( resourceHandle ); + + // create the resource & add it to the array (am I leaking huge amounts of memory here, or are they dealloced automatically?) + { + NSString *name = [NSString stringWithCString:&nameStr[1] length:nameStr[0]]; + NSString *type = [NSString stringWithCString:(char *) &resType length:4]; + NSNumber *size = [NSNumber numberWithLong:sizeLong]; + NSNumber *resID = [NSNumber numberWithShort:resIDShort]; + NSNumber *attributes = [NSNumber numberWithShort:attrsShort]; + NSData *data = [NSData dataWithBytes:*resourceHandle length:sizeLong]; + Resource *resource = [Resource resourceOfType:type andID:resID withName:name andAttributes:attributes data:data ofLength:size]; + [resources addObject:resource]; + [resource autorelease]; + } + + HUnlock( resourceHandle ); + ReleaseResource( resourceHandle ); + } + } + + // save resource map and clean up + UseResFile( oldResFile ); + return YES; +} + +- (BOOL)writeToFile:(NSString *)fileName ofType:(NSString *)type +{ + BOOL succeeded = NO; + OSStatus error = noErr; + FSRef *parentRef = (FSRef *) NewPtrClear( sizeof(FSRef) ); + FSRef *fileRef = (FSRef *) NewPtrClear( sizeof(FSRef) ); + FSSpec *fileSpec = (FSSpec *) NewPtrClear( sizeof(FSSpec) ); + SInt16 fileRefNum = 0; + + // create and open file for writing + error = FSPathMakeRef( [[fileName stringByDeletingLastPathComponent] cString], parentRef, nil ); + if( otherFork ) + { + unichar *uniname = (unichar *) NewPtrClear( sizeof(unichar) *256 ); + [[fileName lastPathComponent] getCharacters:uniname]; + error = FSCreateResourceFile( parentRef, [[fileName lastPathComponent] length], (UniChar *) uniname, kFSCatInfoNone, nil, otherFork->length, (UniChar *) &otherFork->unicode, fileRef, fileSpec ); + if( !error ) + error = FSOpenResourceFile( fileRef, otherFork->length, (UniChar *) &otherFork->unicode, fsWrPerm, &fileRefNum); + } + else + { + unichar *uniname = (unichar *) NewPtrClear( sizeof(unichar) *256 ); + [[fileName lastPathComponent] getCharacters:uniname]; + error = FSCreateResourceFile( parentRef, [[fileName lastPathComponent] length], (UniChar *) uniname, kFSCatInfoNone, nil, 0, nil, fileRef, fileSpec ); + if( !error ) + error = FSOpenResourceFile( fileRef, 0, nil, fsWrPerm, &fileRefNum); + } + + // write resource array to file + if( fileRefNum && !error ) + succeeded = [self writeResourceMap:fileRefNum]; + + // tidy up loose ends + if( fileRefNum ) FSClose( fileRefNum ); + DisposePtr( (Ptr) fileRef ); + return succeeded; +} + +- (BOOL)writeToURL:(NSURL *)url ofType:(NSString *)type +{ + return NO; +} + +- (BOOL)writeResourceMap:(SInt16)fileRefNum +{ + OSStatus error = noErr; + unsigned long i; + SInt16 oldResFile = CurResFile(); + UseResFile( fileRefNum ); + + for( i = 0; i < [resources count]; i++ ) + { + Resource *resource = [resources objectAtIndex:i]; + + Str255 nameStr; + ResType resType; + short resIDShort = [[resource resID] shortValue]; + long sizeLong = [[resource size] longValue]; + short attrsShort = [[resource attributes] shortValue]; + Handle resourceHandle = NewHandleClear( sizeLong ); + + nameStr[0] = [[resource name] cStringLength]; + BlockMoveData( [[resource name] cString], &nameStr[1], nameStr[0] ); + + [[resource type] getCString:(char *) &resType maxLength:4]; + + HLockHi( resourceHandle ); + [[resource data] getBytes:*resourceHandle]; + HUnlock( resourceHandle ); + + AddResource( resourceHandle, resType, resIDShort, nameStr ); + if( ResError() == addResFailed ) + { + NSLog( @"Saving failed; could not add resource \"%@\" of type %@ to file.", [resource name], [resource type] ); + error = addResFailed; + } + else + { + SetResAttrs( resourceHandle, attrsShort ); + ChangedResource( resourceHandle ); + UpdateResFile( fileRefNum ); + } + } + + // save resource map and clean up + UseResFile( oldResFile ); + return error? NO:YES; +} + +- (NSOutlineView *)outlineView +{ + return outlineView; +} + +- (ResourceDataSource *)dataSource +{ + return dataSource; +} + +@end \ No newline at end of file diff --git a/Cocoa/Classes/SizeFormatter.h b/Cocoa/Classes/SizeFormatter.h new file mode 100644 index 0000000..d0efe87 --- /dev/null +++ b/Cocoa/Classes/SizeFormatter.h @@ -0,0 +1,9 @@ +#import + +@interface SizeFormatter : NSNumberFormatter +{ +} + +- (NSString *)stringForObjectValue:(id)obj; + +@end diff --git a/Cocoa/Classes/SizeFormatter.m b/Cocoa/Classes/SizeFormatter.m new file mode 100644 index 0000000..ee0a316 --- /dev/null +++ b/Cocoa/Classes/SizeFormatter.m @@ -0,0 +1,45 @@ +#import "SizeFormatter.h" + +@implementation SizeFormatter + +- (void)awakeFromNib +{ + [self setFormat:@"#,##0.0"]; + [self setLocalizesFormat:YES]; + [self setAllowsFloats:YES]; +} + +- (NSString *)stringForObjectValue:(id)obj +{ + NSMutableString *string = [NSMutableString string]; + float value = [obj floatValue]; + int power = 0; + + while( value >= 1024 && power <= 30 ) + { + power += 10; // 10 == KB, 20 == MB, 30 == GB + value /= 1024; + } + + switch( power ) + { + case 0: + [string appendFormat:@"%.0f", value]; + break; + + case 10: + [string appendFormat:@"%.1f KB", value]; + break; + + case 20: + [string appendFormat:@"%.1f MB", value]; + break; + + default: + [string appendFormat:@"%.1f GB", value]; + break; + } + return string; +} + +@end diff --git a/Cocoa/English.lproj/AboutPanel.nib/classes.nib b/Cocoa/English.lproj/AboutPanel.nib/classes.nib new file mode 100644 index 0000000..b9b4b09 --- /dev/null +++ b/Cocoa/English.lproj/AboutPanel.nib/classes.nib @@ -0,0 +1,4 @@ +{ + IBClasses = ({CLASS = FirstResponder; LANGUAGE = ObjC; SUPERCLASS = NSObject; }); + IBVersion = 1; +} \ No newline at end of file diff --git a/Cocoa/English.lproj/AboutPanel.nib/info.nib b/Cocoa/English.lproj/AboutPanel.nib/info.nib new file mode 100644 index 0000000..dc83241 --- /dev/null +++ b/Cocoa/English.lproj/AboutPanel.nib/info.nib @@ -0,0 +1,16 @@ + + + + + IBUserGuides + + AboutPanel + + guideLocations + + guidesLocked + NO + + + + diff --git a/Cocoa/English.lproj/AboutPanel.nib/objects.nib b/Cocoa/English.lproj/AboutPanel.nib/objects.nib new file mode 100644 index 0000000000000000000000000000000000000000..a8a6ab197014929fb95e09c20b55cfa9af24d2fc GIT binary patch literal 1996 zcmb7F-EZ4e6hF5~LqF0r9qV8dND-QZ3aHA;7^o6jx}ZTf$fl**9uPvVbJLl*apt(| zDj_ka*Y_34%B%W6fv1gg>?CF51zh>w8~gZt z{C>Z48tt{_N<3e-Os-7Zkv|1X3n6x8bK^%(PJY5w>m9{XZ1`P>u52!R0*)J+WM;~i zF&#b}s)~*@ecGSReK2DwlRD2?rdFI{!I#Z*Rj^mvexxG>)4DwTD4xybRMXJ)Ga8?@ z;SV8V*&LmRgy_xYPV;ifDCW5dRaY#zjt?T9#;ef0!gQKp$4pb110kNt=E7su@1%s$ z&ji0@9n-j;-zP+OHkUDq76u(!a%+C22LNggUMFn$*=C=<2tcb_a~=8u#w_cJt8^^h4~||++1ane#h?I zMzciIjH=!-T?4d{2n`!`q${pIjpBX8FA<=aXrXwO0KERi4j?zsd*2yumJzySYWPt8 zpcJeAft&xO{?$ILn`)0@?YDx$*Kb384O9Ax5(+3EpH`+gDQW*T$}iqz8DFC2T0-R( zV%3Qn!~;rnoVj9QaS%z-bY4`ZdHK|&p_usz>=Y^EHr!9808*(Zs@jQ0#1GvFi-a2( zDS;#et%9T*Gp5Risvg#j+yvPX5d$dJQc2fTg+w%tcuV;$G8)k|4O5nN)N=oz%60kd)c!r~un>IYW79iTh8z`>LOx7R7r8S6(BjhR;>bSm$ z6bhr*vhk1eM$5}+;NBHK{ma$T(piN4?Cz(W+-37e%)Oa=RCZA6@qIR{*l>*~p*>J9 z(ek7FOGuCCq{*)J;8hWrimZ9Hu#wJc#k|qx+Gc-MMW3bx)-+nvYNW$vpV{nssItnP zOE@SO7D3{I4R=Z@a-Ggml8mf5OhoBpIBs`>9a*k3Wb1PqwvnrjH4N;n)V%F0-e%3) z^1Y3kw_fwRJRcpfH}KtW-W5E?X->RcPM(}Ca#MQSNJ-Dwq%mWK(Y#GEKP0^^+7C-_ zgZ2^Wt1W)1Valz#&NLh;T3 literal 0 HcmV?d00001 diff --git a/Cocoa/English.lproj/Application.nib/classes.nib b/Cocoa/English.lproj/Application.nib/classes.nib new file mode 100644 index 0000000..e9f8f99 --- /dev/null +++ b/Cocoa/English.lproj/Application.nib/classes.nib @@ -0,0 +1,29 @@ +{ + IBClasses = ( + { + ACTIONS = { + openResource = id; + playSound = id; + showCreateResourceSheet = id; + showInfo = id; + showPrefs = id; + }; + CLASS = ApplicationDelegate; + LANGUAGE = ObjC; + SUPERCLASS = NSObject; + }, + { + ACTIONS = { + openResource = id; + playSound = id; + showCreateResourceSheet = id; + showInfo = id; + showPrefs = id; + }; + CLASS = FirstResponder; + LANGUAGE = ObjC; + SUPERCLASS = NSObject; + } + ); + IBVersion = 1; +} \ No newline at end of file diff --git a/Cocoa/English.lproj/Application.nib/info.nib b/Cocoa/English.lproj/Application.nib/info.nib new file mode 100644 index 0000000..477300e --- /dev/null +++ b/Cocoa/English.lproj/Application.nib/info.nib @@ -0,0 +1,12 @@ + + + + + IBDocumentLocation + 156 127 432 426 0 74 1280 928 + IBMainMenuLocation + 205 655 347 44 0 74 1280 928 + IBUserGuides + + + diff --git a/Cocoa/English.lproj/Application.nib/objects.nib b/Cocoa/English.lproj/Application.nib/objects.nib new file mode 100644 index 0000000000000000000000000000000000000000..af20fd9103a372b1285fbb736969add262e99781 GIT binary patch literal 6186 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 XSj+8w-936jR~|K#G#$~!^Y(uM8I!Cx literal 0 HcmV?d00001 diff --git a/Cocoa/English.lproj/InfoPlist.strings b/Cocoa/English.lproj/InfoPlist.strings new file mode 100644 index 0000000..b0c70fc --- /dev/null +++ b/Cocoa/English.lproj/InfoPlist.strings @@ -0,0 +1,6 @@ +/* Localized versions of Info.plist keys */ + +CFBundleName = "ResKnife"; +CFBundleShortVersionString = "Development Version"; +CFBundleGetInfoString = "ResKnife 0.4d3, Copyright 2001 Nicholas Shanks."; +NSHumanReadableCopyright = "Copyright © 2001 Nicholas Shanks."; diff --git a/Cocoa/English.lproj/InfoWindow.nib/classes.nib b/Cocoa/English.lproj/InfoWindow.nib/classes.nib new file mode 100644 index 0000000..ada9d14 --- /dev/null +++ b/Cocoa/English.lproj/InfoWindow.nib/classes.nib @@ -0,0 +1,20 @@ +{ + IBClasses = ( + {CLASS = FirstResponder; LANGUAGE = ObjC; SUPERCLASS = NSObject; }, + {CLASS = InfoWindow; LANGUAGE = ObjC; SUPERCLASS = NSPanel; }, + { + ACTIONS = {attributesChanged = id; }; + CLASS = InfoWindowController; + LANGUAGE = ObjC; + OUTLETS = { + attributesMatrix = id; + iconView = id; + nameView = id; + resIDView = id; + typeView = id; + }; + SUPERCLASS = NSWindowController; + } + ); + IBVersion = 1; +} \ No newline at end of file diff --git a/Cocoa/English.lproj/InfoWindow.nib/info.nib b/Cocoa/English.lproj/InfoWindow.nib/info.nib new file mode 100644 index 0000000..f843ace --- /dev/null +++ b/Cocoa/English.lproj/InfoWindow.nib/info.nib @@ -0,0 +1,18 @@ + + + + + IBDocumentLocation + 377 192 384 449 0 74 1280 928 + IBUserGuides + + InfoWindow + + guideLocations + + guidesLocked + NO + + + + diff --git a/Cocoa/English.lproj/InfoWindow.nib/objects.nib b/Cocoa/English.lproj/InfoWindow.nib/objects.nib new file mode 100644 index 0000000000000000000000000000000000000000..5fe4a1da5a20056a4af24fab66c580ee96bf6f52 GIT binary patch literal 2490 zcma)8U2NM_6u!=1(>3kdt_5`nC~Okic!E)cs=?A`8Y|nfvQ+SZKyn?YGfU#ian|+; zIlI0uydW#JV#YR;DOJ`@42ezYMkS=GGDs6lnx<(lmG*>qc7`ib?&X1rY)XD#$|mKxn4DD=Ih__2rZdV(J5A7z#1rFr zRh6?bF`Yi-K932i9L1IeD?49LnLc|KoBO^u?!h7K!-ufxKSOl zaI;Yl{Syc~24TmxhFxoqaphKn?AlFo`#XTSwG{+%x2_R3Ln$M2 zgwAmq`CcPGBLH5#`WiZeuA$2oXH-xt9M(upBbQJMuMJ1EBbACq1wnxA0Ht>~85U;8 zmAcP}u9XBJk;mo#wMMu-k!*SA3gx z=5mq;pphSje1)S2-`!k+W`M41>=JJnVV~jl+0__0?(Ry+c|{QYuyIQz99(mfDx798 zpIpz)Oms{tVp`@s&m0D5ueSO7QNIfo)>fO)aalOCh3u-veRix)am6*k@S0A-lX+zZ zheBI?|6$VQwfFE*k)QoHD|Q?EeTC!L#Vsy_r}(5Kw=DIBzc(O@9!0O34LN4lmD}b6 z+gz)f4{h^W)vPR;_ieKZ>FtdtylForW@gj8Dr(&0BJGx>R;7hqKF#-$=6mGzYT?&; zD0ZpT4Rh43#xNckxMO#;&JIR@Dg|&9vZ~I}0D_Tsv>?&^c9~l_H@rF&1@lJL zv@7OP)x2Yy>s9lfZEh@Dy`~L#Fv7iY{t7b};W>hH@}=DH$+;|?N#;Ey(dZ7GRG@YT z)!Jpuw6?3g%FJsZYaiG^!!vA7KEQgo-Ef4NYunTSYXd~>k(>9o+Z|x$63Dz$hM5nx zufN93b&vt?rh71{f(Xt?H;HDY?ZKeeuXDx*wEVKwi4(pL5C5ZLdKM16ES#^h(pRiu zKv3K%Sx!$$v$%RpT)--|>V_{8FeK^kwY5KBp{rHU!ziY7AfAXz(TsB?FdmoD^Z04!Ubk~6 z;)GvxetXKPhMe_b$M>9bW5BuA?`#B}hkXut)>-77C(k&6=N-G#dC==DbvS?ScB~#J e^n!DLr?tl-IGlPX#$&P|^G6h*IVUS;82bkTqyU`& literal 0 HcmV?d00001 diff --git a/Cocoa/English.lproj/Localizable.strings b/Cocoa/English.lproj/Localizable.strings new file mode 100644 index 0000000..c634159 --- /dev/null +++ b/Cocoa/English.lproj/Localizable.strings @@ -0,0 +1,5 @@ +/* Localized versions of application-critical strings */ + +/* File window default resource names */ +"Untitled Resource" = "Untitled Resource"; +"Custom Icon" = "Custom Icon"; diff --git a/Cocoa/English.lproj/PrefsWindow.nib/classes.nib b/Cocoa/English.lproj/PrefsWindow.nib/classes.nib new file mode 100644 index 0000000..0b3766f --- /dev/null +++ b/Cocoa/English.lproj/PrefsWindow.nib/classes.nib @@ -0,0 +1,13 @@ +{ + IBClasses = ( + {CLASS = FirstResponder; LANGUAGE = ObjC; SUPERCLASS = NSObject; }, + { + ACTIONS = {acceptPrefs = id; cancelPrefs = id; resetToDefault = id; }; + CLASS = PrefsWindowController; + LANGUAGE = ObjC; + OUTLETS = {autosaveIntervalField = id; dataProtectionMatrix = id; }; + SUPERCLASS = NSWindowController; + } + ); + IBVersion = 1; +} \ No newline at end of file diff --git a/Cocoa/English.lproj/PrefsWindow.nib/info.nib b/Cocoa/English.lproj/PrefsWindow.nib/info.nib new file mode 100644 index 0000000..a0fbaa9 --- /dev/null +++ b/Cocoa/English.lproj/PrefsWindow.nib/info.nib @@ -0,0 +1,18 @@ + + + + + IBDocumentLocation + 70 184 468 280 0 74 1280 928 + IBUserGuides + + PrefsWindow + + guideLocations + + guidesLocked + NO + + + + diff --git a/Cocoa/English.lproj/PrefsWindow.nib/objects.nib b/Cocoa/English.lproj/PrefsWindow.nib/objects.nib new file mode 100644 index 0000000000000000000000000000000000000000..0734ac2749d55a1e6546c1e01c4130c4117b8945 GIT binary patch literal 2490 zcmZuzU2oe|7(PzYv|ZD6UE7Im1%d_|8&?boO=YmO9c5)K-Ii*THgVxPC!JYp7spxK zI~~ z`NFfT>B>tlOK*n>AqVi?+9q$2ERl#zl0+nL5DjkyV+xHzs+|cEDG4P~3R$L!%lZV< z_6&tVXGK^v{TgGfK;v6+#JlUFOs6nvizhD7%%rL$sYdcPyTu7=f@SLTQH*cdW`VE> zN1duZ*aydFv${Bzru~{G&Jf0$;|YJ5FNC-T-0X8hN_M4?dj?kAHt`tbL1Z*a$Y|8A z03$vuBui3^2!ws~iAPed<6$_t|A1 zjc2}RPfebu%Bwd%$>rbO>d&>r5CTgz=dZD#6pIPC0dY2{rd5P$Da?Y11@IkC%w%*r z!Q-Gy$MplEbZuNyvr5tvwb*y1APb~Yf?)A+c;$OYwsOh({wl$yg=iG|Oa#R@4cKoV zzS2p^mCha5zpch9;Xt9TWkOc6I?e2=`)>U!QoAi8`Ki}%;rl0#E_~jIXirs!Gvdqr z#1Y*?B*9;SfFX2T=KkuqY|r~Fa*txmj6@9b&+FL3yAh?y_8yY)^!4@e-1U>^#`bnl zz##~Cg~GYe^KkY;A!t8LKhK;)sC^l#p*)R&%)j2tXD6d6IZdNeSE&+3-9nM9TUzv@ zG>|)e;{5g!1fJX6KE^xzM$`pb_ucNpDtK}^bO z{fK$jz!8!=ef<2kxn#0GIhJK#>z?~HC)rxb! z=qy(XyPbk>UcINiXgNi!p>z0M1oK(UBSVOU!`nAcho z^4n|H)fSeZIvN<1^8p(FuwZXOWTDW^<$B_YxI9+vocK{t0#(f*KYLx!-}2Ww^DC>I zo(`Tm`lwnh=6$@$FxKtq8OiDh(MC1fE3=&A?j%ZVy~6r@^OJ#-*e|ZgqpZgV%A+{jbz{k?9n!xi?s^Sj(x!Ch>2!w21# iUiZHz+_j#<(*?paX3y}zpem{2kS0#hDOI~>S^opR&GG{P literal 0 HcmV?d00001 diff --git a/Cocoa/English.lproj/ResourceDocument.nib/classes.nib b/Cocoa/English.lproj/ResourceDocument.nib/classes.nib new file mode 100644 index 0000000..8a0f5e2 --- /dev/null +++ b/Cocoa/English.lproj/ResourceDocument.nib/classes.nib @@ -0,0 +1,53 @@ +{ + IBClasses = ( + {CLASS = AttributesFormatter; LANGUAGE = ObjC; SUPERCLASS = NSFormatter; }, + { + ACTIONS = { + hideCreateResourceSheet = id; + showCreateResourceSheet = id; + typePopupSelection = id; + }; + CLASS = CreateResourceSheetController; + LANGUAGE = ObjC; + OUTLETS = { + attributesMatrix = id; + cancelButton = id; + createButton = id; + dataSource = id; + nameView = id; + parent = id; + resIDView = id; + typePopup = id; + typeView = id; + }; + SUPERCLASS = NSWindowController; + }, + {CLASS = FirstResponder; LANGUAGE = ObjC; SUPERCLASS = NSObject; }, + {CLASS = NameFormatter; LANGUAGE = ObjC; SUPERCLASS = NSFormatter; }, + { + CLASS = OutlineViewDelegate; + LANGUAGE = ObjC; + OUTLETS = { + attributesFormatter = id; + nameFormatter = id; + sizeFormatter = id; + window = id; + }; + SUPERCLASS = NSObject; + }, + { + CLASS = ResourceDataSource; + LANGUAGE = ObjC; + OUTLETS = {createResourceSheetController = id; outlineView = id; window = id; }; + SUPERCLASS = NSObject; + }, + { + CLASS = ResourceDocument; + LANGUAGE = ObjC; + OUTLETS = {dataSource = id; outlineView = id; }; + SUPERCLASS = NSDocument; + }, + {CLASS = SizeFormatter; LANGUAGE = ObjC; SUPERCLASS = NSNumberFormatter; } + ); + IBVersion = 1; +} \ No newline at end of file diff --git a/Cocoa/English.lproj/ResourceDocument.nib/info.nib b/Cocoa/English.lproj/ResourceDocument.nib/info.nib new file mode 100644 index 0000000..54cdad9 --- /dev/null +++ b/Cocoa/English.lproj/ResourceDocument.nib/info.nib @@ -0,0 +1,27 @@ + + + + + IBDocumentLocation + 14 132 480 547 0 74 1280 928 + IBMainMenuLocation + 52 694 73 138 0 74 1280 928 + IBUserGuides + + CreateResourceSheet + + guideLocations + + guidesLocked + NO + + Window + + guideLocations + + guidesLocked + NO + + + + diff --git a/Cocoa/English.lproj/ResourceDocument.nib/objects.nib b/Cocoa/English.lproj/ResourceDocument.nib/objects.nib new file mode 100644 index 0000000000000000000000000000000000000000..d3ae9fb581874600f2bc1842fc14180055e8e5aa GIT binary patch literal 6842 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 Tab0~p5)W@kz_EA6>Av*;tf$%6 literal 0 HcmV?d00001 diff --git a/Cocoa/Resources/Icon file.icns b/Cocoa/Resources/Icon file.icns new file mode 100644 index 0000000000000000000000000000000000000000..b3a060ce4aa933288a580c71eeebd99e2298de16 GIT binary patch literal 6918 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 literal 0 HcmV?d00001 diff --git a/Cocoa/Resources/ResKnife.icns b/Cocoa/Resources/ResKnife.icns new file mode 100644 index 0000000000000000000000000000000000000000..8a6833a2031e9828adb987d0cbf44acc05bb2459 GIT binary patch literal 39176 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 literal 0 HcmV?d00001 diff --git a/Cocoa/Resources/defaults.plist b/Cocoa/Resources/defaults.plist new file mode 100644 index 0000000..cd275c8 --- /dev/null +++ b/Cocoa/Resources/defaults.plist @@ -0,0 +1,6 @@ +{ + PreserveBackups = YES; + Autosave = YES; + AutosaveInterval = 5; + DeleteResourceWarning = YES; +} \ No newline at end of file diff --git a/Cocoa/main.c b/Cocoa/main.c new file mode 100644 index 0000000..a1dfd3b --- /dev/null +++ b/Cocoa/main.c @@ -0,0 +1,6 @@ +#import + +int main(int argc, const char *argv[]) +{ + return NSApplicationMain(argc, argv); +} \ No newline at end of file diff --git a/Hex Editor/Classes/Events.cpp b/Hex Editor/Classes/Events.cpp new file mode 100644 index 0000000..edab04a --- /dev/null +++ b/Hex Editor/Classes/Events.cpp @@ -0,0 +1,998 @@ +#include "Events.h" +#include "HexWindow.h" +#include "Utility.h" + +extern globals g; +extern prefs p; + + /******************/ + /* EVENT HANDLING */ +/******************/ + +/*** CARBON WINDOW EVENT HANDLER ***/ +pascal OSStatus CarbonWindowEventHandler( EventHandlerCallRef handler, EventRef event, void *userData ) +{ + #pragma unused( handler ) + OSStatus error = eventNotHandledErr; + Plug_PlugInRef plugRef = (Plug_PlugInRef) userData; + WindowRef window = GetUserFocusWindow(); + + // get event type + UInt32 eventClass = GetEventClass( event ); + UInt32 eventKind = GetEventKind( event ); + + // get event parameters + if( eventClass == kEventClassWindow ) + GetEventParameter( event, kEventParamDirectObject, typeWindowRef, null, sizeof(WindowRef), null, &window ); + if( !window ) return error; + Plug_WindowRef plugWindow = Host_GetPlugWindowFromWindowRef( window ); + if( !plugWindow ) return error; + HexWindowPtr hexWindow = (HexWindowPtr) Host_GetWindowRefCon( plugWindow ); + if( !hexWindow ) return error; + + // get window rect + Rect windowBounds; + GetWindowPortBounds( window, &windowBounds ); + + // handle event + static EventHandlerRef resizeEventHandlerRef = null; + switch( eventClass ) + { + case kEventClassWindow: + switch( eventKind ) + { + case kEventWindowClose: + delete hexWindow; + break; + + case kEventWindowActivated: + case kEventWindowDeactivated: + if( hexWindow->activeWindow && hexWindow->insertionPointVisable ) + BlinkInsertionPoint( null, window ); // this has to be done before the window is marked as deactivated +// hexWindow->activeWindow = !hexWindow->activeWindow; // bug: OS X is sending the event twice (naughty Apple!), so i shall do this more correctly as follows + if( eventKind == kEventWindowActivated ) + hexWindow->activeWindow = true; + else hexWindow->activeWindow = false; + InvalidateWindowRect( window, &windowBounds ); + break; + + case kEventWindowBoundsChanging: + error = hexWindow->BoundsChanging( event ); + break; + + case kEventWindowBoundsChanged: + error = hexWindow->BoundsChanged( event ); + break; + + case kEventWindowDrawContent: + error = hexWindow->DrawContent( event ); + break; + + case kEventWindowHandleContentClick: + { // get mouse + Point mouse; + error = GetEventParameter( event, kEventParamMouseLocation, typeQDPoint, null, sizeof(Point), null, &mouse ); + if( !error ) + { + MakeLocal( window, mouse, &mouse ); + + // get modifier keys + UInt32 modifiers = null; + error = GetEventParameter( event, kEventParamKeyModifiers, typeUInt32, null, sizeof(UInt32), null, &modifiers ); + if( error ) break; + + // identify click is in edit boxes & act accordingly + Boolean clickHex = false; // clicked in hex rect? + Boolean clickAscii = false; // clicked in ascii rect? - neither means not editing + Plug_WindowRef plugWindow = Host_GetPlugWindowFromWindowRef( window ); + HexWindowPtr hexWindow = (HexWindowPtr) Host_GetWindowRefCon( plugWindow ); + if( PtInRect( mouse, &hexWindow->hexRect ) ) { clickHex = true; hexWindow->editingHex = true; } + if( PtInRect( mouse, &hexWindow->asciiRect ) ) { clickAscii = true; hexWindow->editingHex = false; } + if( clickHex || clickAscii ) error = HandleEditClick( window, event, mouse, (EventModifiers) LoWord(modifiers) ); + else error = eventNotHandledErr; + } + else error = eventNotHandledErr; + } break; + } + break; + + case kEventClassKeyboard: + switch( eventKind ) + { + case kEventRawKeyDown: + case kEventRawKeyRepeat: + { signed char charCode; // key character pressed + UInt32 modifiers = null; + error = GetEventParameter( event, kEventParamKeyMacCharCodes, typeChar, null, sizeof(char), null, &charCode ); + if( error ) break; + error = GetEventParameter( event, kEventParamKeyModifiers, typeUInt32, null, sizeof(UInt32), null, &modifiers ); + if( error ) break; + HandleKeyDown( window, charCode, (EventModifiers) LoWord(modifiers) ); + } break; + } + + // calculate new scrollbar values & redraw window + hexWindow->UpdateHexInfo(); + InvalidateWindowRect( window, &windowBounds ); + break; + } + return error; +} + +/*** CARBON HUMAN INTERFACE EVENT HANDLER ***/ +pascal OSStatus CarbonHIEventHandler( EventHandlerCallRef handler, EventRef event, void *userData ) +{ + #pragma unused( handler, userData ) + OSStatus error = eventNotHandledErr; + WindowRef window = GetUserFocusWindow(); // overridden below for window class events + if( !window ) return error; + + // get event type + UInt32 eventClass = GetEventClass( event ); + UInt32 eventKind = GetEventKind( event ); + + // get event parameters + Plug_WindowRef plugWindow = Host_GetPlugWindowFromWindowRef( window ); + if( !plugWindow ) return error; + HexWindowPtr hexWindow = (HexWindowPtr) Host_GetWindowRefCon( plugWindow ); + if( !hexWindow ) return error; + + // handle event + switch( eventClass ) + { + case kEventClassMenu: + switch( eventKind ) + { + case kEventMenuEnableItems: + { Plug_ResourceRef resource = Host_GetTargetResource( plugWindow ); + Boolean canPlaySound = false; + if( Host_GetResourceType( resource ) == soundListRsrc ) + canPlaySound = true; + + // get host to set menus so we can modify them + Host_UpdateMenus( resource ); + + // edit menu + EnableCommand( null, kHICommandUndo, false ); + EnableCommand( null, kHICommandRedo, false ); + EnableCommand( null, kHICommandCut, hexWindow->selStart != hexWindow->selEnd ); + EnableCommand( null, kHICommandCopy, hexWindow->selStart != hexWindow->selEnd ); + EnableCommand( null, kHICommandPaste, true ); // bug + EnableCommand( null, kHICommandClear, hexWindow->selStart != hexWindow->selEnd ); + error = noErr; + } break; + } + break; + + case kEventClassCommand: + HICommand command; + Plug_ResourceRef resource = Host_GetTargetResource( plugWindow ); + error = GetEventParameter( event, kEventParamDirectObject, typeHICommand, null, sizeof(HICommand), null, &command ); + if( error ) return eventNotHandledErr; + switch( eventKind ) + { + case kEventCommandProcess: + switch( command.commandID ) + { + /* case kHICommandOK: + case kHICommandCancel: + case kHICommandQuit: + case kHICommandUndo: + case kHICommandRedo: + case kHICommandCut: + SendEventToWindow( copyEvent, window ); + SendEventToWindow( clearEvent, window ); + error = noErr; + break; + */ + case kHICommandCopy: + { // copy should be disabled if there isn't a selection, but just in caseÉ + if( hexWindow->selStart == hexWindow->selEnd ) return error; + + // lock the data + Size size; + ScrapRef scrap; + ClearCurrentScrap(); + error = GetCurrentScrap( &scrap ); + if( error ) return error; + SInt8 state = HGetState( hexWindow->data ); + HLock( hexWindow->data ); + if( hexWindow->editingHex ) + { + // copy data with hex formatting + size = 3*(hexWindow->selEnd - hexWindow->selStart) -1; + Ptr hex = NewPtrClear( size ); + Ptr ascii = NewPtrClear( hexWindow->selEnd - hexWindow->selStart ); + BlockMoveData( *hexWindow->data + hexWindow->selStart, ascii, hexWindow->selEnd - hexWindow->selStart ); + AsciiToHex( ascii, hex, hexWindow->selEnd - hexWindow->selStart ); + error = PutScrapFlavor( scrap, kScrapFlavorTypeText, kScrapFlavorMaskNone, size, hex ); + DisposePtr( hex ); + DisposePtr( ascii ); + } + else + { + // copy raw data as byte stream + size = hexWindow->selEnd - hexWindow->selStart; + Ptr ascii = NewPtrClear( size ); + BlockMoveData( *hexWindow->data + hexWindow->selStart, ascii, size ); + error = PutScrapFlavor( scrap, kScrapFlavorTypeText, kScrapFlavorMaskNone, size, ascii ); + DisposePtr( ascii ); + } + HSetState( hexWindow->data, state ); + error = noErr; + } break; + + case kHICommandPaste: + { Size size; + ScrapRef scrap; + error = GetCurrentScrap( &scrap ); + if( error ) return error; + error = GetScrapFlavorSize( scrap, kScrapFlavorTypeText, &size ); + if( error ) return error; + if( size > 0 ) + { + Ptr bytes = NewPtr( size ); + error = GetScrapFlavorData( scrap, kScrapFlavorTypeText, &size, bytes ); + if( !error ) + { + hexWindow->InsertBytes( null, hexWindow->selStart - hexWindow->selEnd, hexWindow->selEnd ); // remove this when using the above + hexWindow->InsertBytes( bytes, size, hexWindow->selStart ); + hexWindow->selStart = hexWindow->selEnd += size; + } + DisposePtr( bytes ); + Host_SetResourceDirty( resource, true ); + } + error = noErr; + } break; + + case kHICommandClear: + hexWindow->InsertBytes( nil, hexWindow->selStart - hexWindow->selEnd, hexWindow->selEnd ); + hexWindow->selEnd = hexWindow->selStart; + Host_SetResourceDirty( resource, true ); + error = noErr; + break; + + case kHICommandSelectAll: + hexWindow->selStart = 0; + hexWindow->selEnd = GetHandleSize( hexWindow->data ); + error = noErr; + break; + + /* case kHICommandHide: + case kHICommandPreferences: + case kHICommandZoomWindow: + case kHICommandMinimizeWindow: + case kHICommandArrangeInFront: + case kHICommandAbout: + */ default: + error = eventNotHandledErr; + break; + } + break; + + case kEventCommandUpdateStatus: + error = eventNotHandledErr; + break; + + default: + error = eventNotHandledErr; + break; + } + break; + } + + // most calls need window updating, so do it here + Rect windowBounds; + hexWindow->UpdateHexInfo(); + GetWindowPortBounds( window, &windowBounds ); + InvalidateWindowRect( window, &windowBounds ); + return error; +} + +#if !TARGET_API_MAC_CARBON + +/*** CLASSIC WINDOW EVENT HANDLER ***/ +pascal OSStatus ClassicWindowEventHandler( EventRecord *event, UInt32 eventKind, void *userData ) +{ + #pragma unused( event, userData ) + OSStatus error = eventNotHandledErr; +/* Plug_PlugInRef plugRef = (Plug_PlugInRef) userData; + WindowRef window; + GetEventParameter( event, kEventParamDirectObject, typeWindowRef, null, sizeof(WindowRef), null, &window ); +*/ + switch( eventKind ) + { + case kEventWindowDrawContent: + error = DrawWindow( window ); + break; + + case kEventWindowHandleContentClick: + SysBeep(0); + break; + } + return error; +} + +#endif + + /*****************/ + /* WINDOW EVENTS */ +/*****************/ + +pascal OSStatus ConstrainWindowResize( EventHandlerCallRef handler, EventRef event, void *userData ) +{ + #pragma unused( handler ) + Rect windowBounds; + HexWindowPtr hexWindow = (HexWindowPtr) userData; + OSStatus error = GetEventParameter( event, kEventParamCurrentBounds, typeQDRectangle, null, sizeof(Rect), null, &windowBounds ); + if( error ) return eventNotHandledErr; + + // constrain window width + UInt8 modulo = (windowBounds.right - windowBounds.left - 13*kHexCharWidth - kScrollBarWidth) % (4*kDataBlockWidth); + if( modulo < (2*kDataBlockWidth) ) windowBounds.right -= modulo; + else windowBounds.right += (4*kDataBlockWidth) - modulo; + + // constrain window height + modulo = (windowBounds.bottom - windowBounds.top - kHeaderHeight - 2*kTextMargin -4) % kHexLineHeight; + if( modulo < (kHexLineHeight/2) ) windowBounds.bottom -= modulo; + else windowBounds.bottom += kHexLineHeight - modulo; + + // update window rect to constrained version + if( (windowBounds.bottom - windowBounds.top) < kMinimumWindowHeight ) windowBounds.bottom = windowBounds.top + kMinimumWindowHeight; + if( (windowBounds.right - windowBounds.left) < kMinimumWindowWidth ) windowBounds.right = windowBounds.left + kMinimumWindowWidth; + error = SetEventParameter( event, kEventParamCurrentBounds, typeQDRectangle, sizeof(Rect), &windowBounds ); + if( error ) error = eventNotHandledErr; + + // resize controls & update hex info + hexWindow->BoundsChanged( event ); + return noErr; +} + + /***************/ + /* USER EVENTS */ +/***************/ + +/*** HANDLE CLICK IN HEX/ASCII REGION ***/ +OSStatus HandleEditClick( WindowRef window, EventRef event, Point mouse, EventModifiers modifiers ) +{ + OSStatus error = eventNotHandledErr; + + // get hex info + Plug_WindowRef plugWindow = Host_GetPlugWindowFromWindowRef( window ); + HexWindowPtr hexWindow = (HexWindowPtr) Host_GetWindowRefCon( plugWindow ); + + // a mouse down event has been recieved and is within hexRect or asciiRect + UInt16 clickLine; // from zero, the line in which the click occoured + UInt16 clickChar; // 0 to 16, the char after the clickloc, or at line end + + // get line clicked on + clickLine = (mouse.v - kHeaderHeight - kTextMargin)/kHexLineHeight + hexWindow->topline; + if( clickLine > hexWindow->lastline ) // beyond last line + { + hexWindow->selStart = hexWindow->selEnd = GetHandleSize( hexWindow->data ); + return noErr; + } + + // get char clicked on -- duplicated in dragging routine + if( hexWindow->editingHex ) clickChar = (mouse.h - kHexCharWidth *10) / (kHexCharWidth*3); + else clickChar = (mouse.h - kHexCharWidth *60) / kHexCharWidth; + + // check for drags + Point globalMouse; + MakeGlobal( window, mouse, &globalMouse ); + Boolean dragging = WaitMouseMoved( globalMouse ); + unsigned long selectFromChar = (clickLine *16) + clickChar; + unsigned long selectToChar = selectFromChar; + + // define selection region + RgnHandle hexRgn = NewRgn(), asciiRgn = NewRgn(), selectedRgn = NewRgn(); + FindSelectedRegions( window, hexRgn, asciiRgn ); + UnionRgn( hexRgn, asciiRgn, selectedRgn ); + + // drag current selection + if( PtInRgn( mouse, selectedRgn ) && dragging ) + { + OSErr error = noErr; + DragReference theDragRef; + ItemReference theItemRef = 1; + FlavorFlags theFlags = nil; + + short resCounter = 0; + Size dataSize = hexWindow->selEnd - hexWindow->selStart; + RgnHandle dragRgn = NewRgn(), subtractRgn = NewRgn(); + Handle dataToDrag = NewHandleClear( dataSize * (hexWindow->editingHex? 3:1) - (hexWindow->editingHex? 1:0) ); + + NewDrag( &theDragRef ); + if( MemError() ) return eventNotHandledErr; + + // get region of dragged items + if( hexWindow->editingHex ) dragRgn = hexRgn; + else dragRgn = asciiRgn; + CopyRgn( dragRgn, subtractRgn ); // duplicate region + InsetRgn( subtractRgn, 2, 2 ); // inset it by 2 pixels + DiffRgn( dragRgn, subtractRgn, dragRgn ); // subtract subRgn from dragRgn + + // get the drag data + EventRecord eventRec; + Boolean eventValid = ConvertEventRefToEventRecord( event, &eventRec ); + if( !eventValid ) eventValid = true; // bug: for some reason the event converter is not returning valid events, but the drag still works + if( dataToDrag && eventValid ) + { + // I can't tell what the previous state of editingHex was, so cannot restore it. + // as such, i will redraw the window with the selection flipped if necessary + hexWindow->DrawContent(); + + SInt8 dataState = HGetState( hexWindow->data ); + SInt8 dragState = HGetState( dataToDrag ); + HLock( hexWindow->data ); + HLock( dataToDrag ); + if( hexWindow->editingHex ) AsciiToHex( *hexWindow->data + hexWindow->selStart, *dataToDrag, dataSize ); + else BlockMoveData( *hexWindow->data + hexWindow->selStart, *dataToDrag, dataSize ); + HSetState( hexWindow->data, dataState ); + HSetState( dataToDrag, dragState ); + + // do the drag + SetPoint( &globalMouse, 0, 0 ); + MakeGlobal( window, globalMouse, &globalMouse ); + OffsetRgn( dragRgn, globalMouse.h, globalMouse.v ); + error = AddDragItemFlavor( theDragRef, theItemRef, kScrapFlavorTypeText, *dataToDrag, GetHandleSize(dataToDrag), theFlags ); + error = TrackDrag( theDragRef, &eventRec, dragRgn ); + + // when dragging from the ACSII pane, drag will contain ÔasciiÕ, when dragging from the HEX pane, drag will contain Ô61 73 63 69 69Õ + } + + // clear up + if( dataToDrag ) DisposeHandle( dataToDrag ); + if( theDragRef ) DisposeDrag( theDragRef ); + if( subtractRgn ) DisposeRgn( subtractRgn ); + if( dragRgn ) DisposeRgn( dragRgn ); + } + + // dragging new selection + else if( dragging ) + { + // remove insetion point if visable + if( hexWindow->activeWindow && hexWindow->insertionPointVisable ) + BlinkInsertionPoint( null, window ); + + Point localMouse; + while( WaitMouseUp() ) + { + // get local mouse co-ords + GetMouse( &mouse ); + MakeLocal( window, mouse, &localMouse ); + + // get line clicked on + clickLine = (localMouse.v - kHeaderHeight - kTextMargin)/kHexLineHeight + hexWindow->topline; + + // get char clicked on + if( hexWindow->editingHex ) clickChar = (localMouse.h - kHexCharWidth *10) / (kHexCharWidth*3); + else clickChar = (localMouse.h - kHexCharWidth *60) / kHexCharWidth; + + // update selection according to mouse position and scroll to maintain visibility + selectToChar = (clickLine *16) + clickChar; + if( selectToChar < 0 ) selectToChar = 0; + if( selectToChar > GetHandleSize(hexWindow->data) -1 ) selectToChar = GetHandleSize(hexWindow->data) -1; + if( selectFromChar < selectToChar ) + { + hexWindow->selStart = selectFromChar; + hexWindow->selEnd = selectToChar +1; +// hexScrollToLine( theWindow, clickLine, kBottomOfWindow ); + } + else if( selectFromChar > selectToChar ) + { + hexWindow->selStart = selectToChar; + hexWindow->selEnd = selectFromChar; +// hexScrollToLine( theWindow, clickLine, kTopOfWindow ); + } + else + { + hexWindow->selStart = selectFromChar; + hexWindow->selEnd = selectToChar +1; + } + + // draw window + hexWindow->DrawContent(); + } + } + + // change cursor position + else + { + // remove insertion point if visable + if( hexWindow->activeWindow && hexWindow->insertionPointVisable ) + BlinkInsertionPoint( null, window ); + + if( modifiers & shiftKey ) // extend selection + { + if( selectFromChar < hexWindow->selStart ) hexWindow->selStart = selectFromChar; + else hexWindow->selEnd = selectFromChar +1; + } + else // normal click + { + if( mouse.v < GetHandleSize(hexWindow->data) ) hexWindow->selStart = selectFromChar; + else hexWindow->selStart = GetHandleSize(hexWindow->data); + hexWindow->selEnd = selectToChar; + } + + if( hexWindow->selStart < 0 ) hexWindow->selStart = 0; + if( hexWindow->selEnd > GetHandleSize(hexWindow->data) ) hexWindow->selEnd = GetHandleSize(hexWindow->data); + + // invalidate window incase there was a selection + Rect windowBounds; + GetWindowPortBounds( window, &windowBounds ); + InvalidateWindowRect( window, &windowBounds ); + } + + DisposeRgn( hexRgn ); + DisposeRgn( asciiRgn ); + DisposeRgn( selectedRgn ); + return noErr; +} + +/*** HANDLE EDIT DRAG ***/ +OSStatus HandleEditDrag( WindowRef window, EventRef event, Point mouse, EventModifiers modifiers ) +{ + #pragma unused( window, event, mouse, modifiers ) + return eventNotHandledErr; +} + +/*** HANDLE KEY DOWN ***/ +OSStatus HandleKeyDown( WindowRef window, unsigned char charCode, EventModifiers modifiers ) +{ + OSStatus error = eventNotHandledErr; + + // get hex info + Plug_WindowRef plugWindow = Host_GetPlugWindowFromWindowRef( window ); + Plug_ResourceRef resource = Host_GetTargetResource( plugWindow ); + HexWindowPtr hexWindow = (HexWindowPtr) Host_GetWindowRefCon( plugWindow ); + + // handle arrow keys + Boolean resEdited = false; + switch( charCode ) + { + case kTabCharCode: + case kEnterCharCode: + case kReturnCharCode: + hexWindow->editingHex = !hexWindow->editingHex; + break; + + case kBackspaceCharCode: // delete + if( hexWindow->selStart == hexWindow->selEnd ) { + if( hexWindow->selStart != 0 ) { + hexWindow->InsertBytes( nil, -1, hexWindow->selEnd ); // delete prev char + hexWindow->selStart = hexWindow->selEnd -= 1; } } + else { + hexWindow->InsertBytes( nil, hexWindow->selStart - hexWindow->selEnd, hexWindow->selEnd ); // remove selection + hexWindow->selEnd = hexWindow->selStart; } + + Host_SetResourceDirty( resource, true ); + break; + + case kDeleteCharCode: // forward delete + if( hexWindow->selStart == hexWindow->selEnd ) + { + if( hexWindow->selStart != GetHandleSize( hexWindow->data ) ) + hexWindow->InsertBytes( nil, -1, hexWindow->selEnd +1 ); // delete next char + } + else + { + hexWindow->InsertBytes( nil, hexWindow->selStart - hexWindow->selEnd, hexWindow->selEnd ); // remove selection + hexWindow->selEnd = hexWindow->selStart; + } + Host_SetResourceDirty( resource, true ); + break; + + case kLeftArrowCharCode: + case kRightArrowCharCode: + case kUpArrowCharCode: + case kDownArrowCharCode: + error = HandleArrowKeyDown( window, charCode, modifiers ); + break; + + default: + if( hexWindow->editingHex ) // editing in hexadecimal + { + Boolean deletePrev = false; // delete prev typing to add new one + if( hexWindow->editedHigh ) // edited high bits already + { + // shift typed char into high bits and add new low char + if( charCode >= 0x30 && charCode <= 0x39 ) charCode -= 0x30; // 0 to 9 + else if( charCode >= 0x61 && charCode <= 0x66 ) charCode -= 0x57; // a to f + else if( charCode >= 0x93 && charCode <= 0x98 ) charCode -= 0x8A; // A to F + else break; + hexWindow->hexChar <<= 4; // store high bit + hexWindow->hexChar += charCode & 0x0F; // add low bit + hexWindow->selStart += 1; + hexWindow->selEnd = hexWindow->selStart; + hexWindow->editedHigh = false; + deletePrev = true; + } + else // editing low bits + { + // put typed char into low bits + if( charCode >= 0x30 && charCode <= 0x39 ) charCode -= 0x30; // 0 to 9 + else if( charCode >= 0x61 && charCode <= 0x66 ) charCode -= 0x57; // a to f + else if( charCode >= 0x93 && charCode <= 0x98 ) charCode -= 0x8A; // A to F + else break; + hexWindow->hexChar = charCode & 0x0F; + hexWindow->editedHigh = true; + } + hexWindow->InsertBytes( nil, hexWindow->selStart - hexWindow->selEnd, hexWindow->selEnd ); // remove selection + hexWindow->selEnd = hexWindow->selStart; + if( deletePrev ) + { + hexWindow->InsertBytes( nil, -1, hexWindow->selStart ); // remove previous hex char + hexWindow->InsertBytes( &hexWindow->hexChar, 1, hexWindow->selStart -1 ); // insert typed char (bug fix hack) + } + else hexWindow->InsertBytes( &hexWindow->hexChar, 1, hexWindow->selStart ); // insert typed char + } + else // editing in ascii + { + hexWindow->InsertBytes( nil, hexWindow->selStart - hexWindow->selEnd, hexWindow->selEnd ); // remove selection + hexWindow->selEnd = hexWindow->selStart; + hexWindow->InsertBytes( &charCode, 1, hexWindow->selStart ); // insert typed char + hexWindow->selStart += 1; + hexWindow->selEnd = hexWindow->selStart; + } + Host_SetResourceDirty( resource, true ); + break; + } + + // check selection is within resource + if( hexWindow->selStart > hexWindow->selEnd ) hexWindow->selStart = hexWindow->selEnd; + if( hexWindow->selStart > GetHandleSize(hexWindow->data) ) hexWindow->selStart = GetHandleSize(hexWindow->data); + if( hexWindow->selEnd > GetHandleSize(hexWindow->data) ) hexWindow->selEnd = GetHandleSize(hexWindow->data); + + // modify key pressed so scroller interperets it correctly + if( modifiers & controlKey ) switch( charCode ) + { + case kUpArrowCharCode: + charCode = kDownArrowCharCode; + break; + + case kDownArrowCharCode: + charCode = kUpArrowCharCode; + break; + + case kLeftArrowCharCode: + charCode = kRightArrowCharCode; + break; + + case kRightArrowCharCode: + charCode = kLeftArrowCharCode; + break; + } + + // get scrollbar +#if TARGET_API_MAC_CARBON + ControlRef scrollbar; + ControlID id = { kScrollbarSignature, 0 }; + GetControlByID( window, &id, &scrollbar ); +#else + ControlRef scrollbar = hexWindow->scrollbar; +#endif + + // scroll to selection + switch( charCode ) + { + case kUpArrowCharCode: + if( hexWindow->selStart /16 < hexWindow->topline +1 ) + SetControlValue( scrollbar, hexWindow->selStart /16 -1 ); + break; + + case kDownArrowCharCode: + if( hexWindow->selEnd /16 > hexWindow->bottomline -1 ) + SetControlValue( scrollbar, hexWindow->selEnd /16 - (hexWindow->bottomline - hexWindow->topline) +1 ); + break; + + case kBackspaceCharCode: + case kLeftArrowCharCode: + if( hexWindow->selStart /16 < hexWindow->topline +1 ) + SetControlValue( scrollbar, hexWindow->selStart /16 -1 ); + break; + + case kRightArrowCharCode: + default: + if( hexWindow->selEnd /16 > hexWindow->bottomline -1 ) + SetControlValue( scrollbar, hexWindow->selEnd /16 - (hexWindow->bottomline - hexWindow->topline) +1 ); + break; + } + return error; +} + +/*** HANDLE ARROW KEY DOWN ***/ +OSStatus HandleArrowKeyDown( WindowRef window, unsigned char charCode, EventModifiers modifiers ) +{ + OSStatus error = noErr; + + // get hex info + Plug_WindowRef plugWindow = Host_GetPlugWindowFromWindowRef( window ); + Plug_ResourceRef resource = Host_GetTargetResource( plugWindow ); + HexWindowPtr hexWindow = (HexWindowPtr) Host_GetWindowRefCon( plugWindow ); + + if( modifiers & optionKey ) switch( charCode ) // move selection + { + case kLeftArrowCharCode: + hexWindow->selStart -= 1; + hexWindow->selEnd -= 1; + break; + case kRightArrowCharCode: + hexWindow->selStart += 1; + hexWindow->selEnd += 1; + break; + case kUpArrowCharCode: + hexWindow->selStart -= 16; + hexWindow->selEnd -= 16; + break; + case kDownArrowCharCode: + hexWindow->selStart += 16; + hexWindow->selEnd += 16; + break; + } + + else if( modifiers & shiftKey ) switch( charCode ) // extend selection + { + case kLeftArrowCharCode: + hexWindow->selStart -= 1; + break; + case kRightArrowCharCode: + hexWindow->selEnd += 1; + break; + case kUpArrowCharCode: + hexWindow->selStart -= 16; + break; + case kDownArrowCharCode: + hexWindow->selEnd += 16; + break; + } + + else if( modifiers & controlKey ) switch( charCode ) // reduce selection + { + case kLeftArrowCharCode: + hexWindow->selEnd -= 1; + break; + case kRightArrowCharCode: + hexWindow->selStart += 1; + break; + case kUpArrowCharCode: + hexWindow->selEnd -= 16; + break; + case kDownArrowCharCode: + hexWindow->selStart += 16; + break; + } + + else switch( charCode ) // move cursor + { + case kLeftArrowCharCode: + if( hexWindow->selStart == hexWindow->selEnd ) + { + if( hexWindow->selStart >= 1 ) hexWindow->selStart -= 1; + if( hexWindow->selEnd >= 1 ) hexWindow->selEnd -= 1; + } + else hexWindow->selEnd = hexWindow->selStart; + hexWindow->editedHigh = false; + break; + + case kRightArrowCharCode: + if( hexWindow->selStart == hexWindow->selEnd ) + { + hexWindow->selStart += 1; + hexWindow->selEnd += 1; + } + else hexWindow->selStart = hexWindow->selEnd; + hexWindow->editedHigh = false; + break; + + case kUpArrowCharCode: + if( hexWindow->selStart == hexWindow->selEnd ) + { + if( hexWindow->selStart >= 16 ) hexWindow->selStart -= 16; + else hexWindow->selStart = 0; + if( hexWindow->selEnd >= 16 ) hexWindow->selEnd -= 16; + else hexWindow->selEnd = 0; + } + else hexWindow->selEnd = hexWindow->selStart; + hexWindow->editedHigh = false; + break; + + case kDownArrowCharCode: + if( hexWindow->selStart == hexWindow->selEnd ) { + hexWindow->selStart += 16; + hexWindow->selEnd += 16; } + else hexWindow->selStart = hexWindow->selEnd; + hexWindow->editedHigh = false; + break; + } + + return error; +} + +/*** FIND SELECTED REGIONS ***/ +OSStatus FindSelectedRegions( WindowRef window, RgnHandle hexRgn, RgnHandle asciiRgn ) +{ + // get hex info structure + Plug_WindowRef plugWindow = Host_GetPlugWindowFromWindowRef( window ); + HexWindowPtr hexWindow = (HexWindowPtr) Host_GetWindowRefCon( plugWindow ); + + // get window bounds + Rect windowBounds, subtractRect; + RgnHandle subtractRgn = NewRgn(); + GetWindowPortBounds( window, &windowBounds ); + + // start and end points of selection relative to first visable char + Boolean clipTop, clipBottom; + signed long startLine = hexWindow->selStart/16 - hexWindow->topline; + signed long endLine = (hexWindow->selEnd - hexWindow->selEnd %16)/16 - hexWindow->topline +1; + + // find hex region + Rect hexSelRect; + SetRect( &hexSelRect, kHexCharWidth *11 -1, startLine * kHexLineHeight, kHexCharWidth *58 +1, endLine * kHexLineHeight ); + OffsetRect( &hexSelRect, 0, kHeaderHeight + kTextMargin + 2 ); + clipTop = hexSelRect.top < kHeaderHeight + kTextMargin +2; + clipBottom = hexSelRect.bottom > windowBounds.bottom - kTextMargin -2; + if( clipTop ) hexSelRect.top = kHeaderHeight + kTextMargin +2; + if( clipBottom ) hexSelRect.bottom = windowBounds.bottom - kTextMargin -2; + RectRgn( hexRgn, &hexSelRect ); + + if( !clipTop ) + { + SetRect( &subtractRect, hexSelRect.left, hexSelRect.top, hexSelRect.left + (hexWindow->selStart % 16) * (kHexCharWidth *3), hexSelRect.top + kHexLineHeight ); + RectRgn( subtractRgn, &subtractRect ); + DiffRgn( hexRgn, subtractRgn, hexRgn ); + } + + if( !clipBottom ) + { + SetRect( &subtractRect, hexSelRect.right - (16 - hexWindow->selEnd % 16) * (kHexCharWidth *3), hexSelRect.bottom - kHexLineHeight, hexSelRect.right, hexSelRect.bottom ); + RectRgn( subtractRgn, &subtractRect ); + DiffRgn( hexRgn, subtractRgn, hexRgn ); + } + + // find ascii region + Rect asciiSelRect; + SetRect( &asciiSelRect, kHexCharWidth *60 -1, startLine * kHexLineHeight, kHexCharWidth *76 +1, endLine * kHexLineHeight ); + OffsetRect( &asciiSelRect, 0, kHeaderHeight + kTextMargin + 2 ); + if( clipTop ) asciiSelRect.top = kHeaderHeight + kTextMargin +2; + if( clipBottom ) asciiSelRect.bottom = windowBounds.bottom - kTextMargin -2; + RectRgn( asciiRgn, &asciiSelRect ); + + if( !clipTop ) + { + SetRect( &subtractRect, asciiSelRect.left, asciiSelRect.top, asciiSelRect.left + (hexWindow->selStart % 16) * kHexCharWidth, asciiSelRect.top + kHexLineHeight ); + RectRgn( subtractRgn, &subtractRect ); + DiffRgn( asciiRgn, subtractRgn, asciiRgn ); + } + + if( !clipBottom ) + { + SetRect( &subtractRect, asciiSelRect.right - (16 - hexWindow->selEnd % 16) * kHexCharWidth, asciiSelRect.bottom - kHexLineHeight, asciiSelRect.right, asciiSelRect.bottom ); + RectRgn( subtractRgn, &subtractRect ); + DiffRgn( asciiRgn, subtractRgn, asciiRgn ); + } + + // clear up + DisposeRgn( subtractRgn ); + return noErr; +} + +/*** UPDATE SELECTION ***/ +OSStatus UpdateSelection( WindowRef window, Boolean editingHex ) +{ + // get controls + ControlRef root, hex, ascii; + GetRootControl( window, &root ); + GetIndexedSubControl( root, 3, &hex ); + GetIndexedSubControl( root, 4, &ascii ); + + // reflect selection + Size actualSize; + ControlEditTextSelectionRec selection; + GetControlData( editingHex? hex:ascii, kControlEditTextPart, kControlEditTextSelectionTag, sizeof(ControlEditTextSelectionRec), &selection, &actualSize ); + if( editingHex ) + { + selection.selStart /= 3; + selection.selEnd = (selection.selEnd +1) /3; + } + else + { + selection.selStart *= 3; + selection.selEnd = (selection.selEnd *3) -1; + } + SetControlData( editingHex? ascii:hex, kControlEditTextPart, kControlEditTextSelectionTag, actualSize, &selection ); + return noErr; +} + + /********************/ + /* CONTROL HANDLING */ +/********************/ + +/*** TRACK SCROLL BAR ***/ +pascal void TrackScrollbar( ControlRef control, short part ) +{ + WindowRef window = GetControlOwner(control); + Plug_WindowRef plugWindow = Host_GetPlugWindowFromWindowRef( window ); + HexWindowPtr hexWindow = (HexWindowPtr) Host_GetWindowRefCon( plugWindow ); + + // decide how many lines to scroll ( and in which direction ) + short startValue, delta, endValue, max; + if( !part ) return; + switch( part ) + { + case kControlUpButtonPart: // up (20) + delta = -1; + break; + + case kControlDownButtonPart: // down (21) + delta = 1; + break; + + case kControlPageUpPart: // page up (22) + delta = hexWindow->topline - hexWindow->bottomline +1; + break; + + case kControlPageDownPart: // page down (23) + delta = hexWindow->bottomline - hexWindow->topline -1; + break; + + default: + delta = 0; + break; + } + + // calculate and correct control value + max = GetControlMaximum( control ); + startValue = GetControlValue( control ); + endValue = startValue + delta; + if( endValue < 0 ) endValue = 0; + if( endValue > max ) endValue = max; + SetControlValue( control, endValue ); + hexWindow->UpdateHexInfo(); + + // update the window +// InvalidateWindowRect( window, &windowBounds ); // doesn't update live scroll immediatly anyway, so may as well not call it + hexWindow->DrawContent(); +} + + /******************/ + /* TIMER ROUTINES */ +/******************/ + +/*** BLINK INSERTION POINT ***/ +pascal void BlinkInsertionPoint( EventLoopTimerRef inTimer, void *inUserData ) +{ + #pragma unused( inTimer ) // inTimer can be null (if routine was not called from a timer) + + // get window's info structure + WindowRef window = (WindowRef) inUserData; + Plug_WindowRef plugWindow = Host_GetPlugWindowFromWindowRef( window ); + HexWindowPtr hexWindow = (HexWindowPtr) Host_GetWindowRefCon( plugWindow ); + + // if not focus window, don't flash cursor + if( !hexWindow->activeWindow ) return; + + // if there's a selection, don't flash cursor + if( hexWindow->selStart != hexWindow->selEnd ) return; + + // if insertion point is not within visable region, don't flash cursor + if( hexWindow->selStart < hexWindow->topline * 16 ) return; + if( hexWindow->selEnd >= (hexWindow->bottomline +1) * 16 ) return; + + GrafPtr oldPort; + GetPort( &oldPort ); + SetPortWindowPort( window ); + Rect blinkRect; + + long theStart = hexWindow->selStart - hexWindow->topline * 16; + long theEnd = hexWindow->selEnd - hexWindow->topline * 16; + if( hexWindow->editingHex ) SetRect( &blinkRect, kHexCharWidth *11 + (theStart %16) * (kHexCharWidth *3) -1, (theStart /16) * kHexLineHeight + kTextMargin +2, + kHexCharWidth *11 + (theStart %16) * (kHexCharWidth *3) +1, (theStart /16) * kHexLineHeight + kTextMargin +2 + kHexLineHeight ); + else SetRect( &blinkRect, kHexCharWidth *60 + (theStart %16) * kHexCharWidth -1, (theStart /16) * kHexLineHeight + kTextMargin +2, + kHexCharWidth *60 + (theStart %16) * kHexCharWidth +1, (theStart /16) * kHexLineHeight + kTextMargin +2 + kHexLineHeight ); + OffsetRect( &blinkRect, 0, kHeaderHeight ); + InvertRect( &blinkRect ); + hexWindow->insertionPointVisable = !hexWindow->insertionPointVisable; + + SetPort( oldPort ); +} \ No newline at end of file diff --git a/Hex Editor/Classes/Events.h b/Hex Editor/Classes/Events.h new file mode 100644 index 0000000..5e5171c --- /dev/null +++ b/Hex Editor/Classes/Events.h @@ -0,0 +1 @@ +#include "Hex Editor.h" #ifndef _ResKnife_HexEditor_Events_ #define _ResKnife_HexEditor_Events_ // classic event handler #if !TARGET_API_MAC_CARBON typedef CALLBACK_API(OSStatus, ClassicEventHandlerProcPtr) (EventRecord *event, UInt32 eventKind, void *userData); typedef STACK_UPP_TYPE(ClassicEventHandlerProcPtr) ClassicEventHandlerUPP; enum { uppClassicEventHandlerProcInfo = 0x00000FF0 }; /* pascal 4_bytes Func(4_bytes, 4_bytes, 4_bytes) */ #ifdef __cplusplus inline ClassicEventHandlerUPP NewClassicEventHandlerUPP(ClassicEventHandlerProcPtr userRoutine) { return (ClassicEventHandlerUPP)NewRoutineDescriptor((ProcPtr)(userRoutine), uppClassicEventHandlerProcInfo, GetCurrentArchitecture()); } #else #define NewClassicEventHandlerUPP(userRoutine) (ClassicEventHandlerUPP)NewRoutineDescriptor((ProcPtr)(userRoutine), uppClassicEventHandlerProcInfo, GetCurrentArchitecture()) #endif #endif /*! * @function CarbonWindowEventHandler * @discussion */ pascal OSStatus CarbonWindowEventHandler( EventHandlerCallRef handler, EventRef event, void *userData ); /*! * @function CarbonHIEventHandler * @discussion */ pascal OSStatus CarbonHIEventHandler( EventHandlerCallRef handler, EventRef event, void *userData ); #if !TARGET_API_MAC_CARBON /*! * @function ClassicWindowEventHandler * @discussion */ pascal OSStatus ClassicWindowEventHandler( EventRecord *event, UInt32 eventKind, void *userData ); #endif /*! * @function ConstrainWindowResize */ pascal OSStatus ConstrainWindowResize( EventHandlerCallRef handler, EventRef event, void *userData ); /*! * @function SizeChanging */ OSStatus SizeChanging( WindowRef window, EventRef event ); /*! * @function SizeChanged */ OSStatus SizeChanged( WindowRef window ); /*! * @function HandleEditClick */ OSStatus HandleEditClick( WindowRef window, EventRef event, Point mouse, EventModifiers modifiers ); /*! * @function HandleEditDrag */ OSStatus HandleEditDrag( WindowRef window, EventRef event, Point mouse, EventModifiers modifiers ); /*! * @function HandleKeyDown */ OSStatus HandleKeyDown( WindowRef window, unsigned char charCode, EventModifiers modifiers ); /*! * @function HandleArrowKeyDown */ OSStatus HandleArrowKeyDown( WindowRef window, unsigned char charCode, EventModifiers modifiers ); /*! * @function FindSelectedRegions */ OSStatus FindSelectedRegions( WindowRef window, RgnHandle hexRgn, RgnHandle asciiRgn ); /*! * @function UpdateSelection */ OSStatus UpdateSelection( WindowRef window, Boolean editingHex ); /*! * @function HexKeyFilter */ pascal ControlKeyFilterResult HexKeyFilter( ControlRef theControl, SInt16 *keyCode, SInt16 *charCode, EventModifiers *modifiers ); /*! * @function AsciiKeyFilter */ pascal ControlKeyFilterResult AsciiKeyFilter( ControlRef theControl, SInt16 *keyCode, SInt16 *charCode, EventModifiers *modifiers ); /*! * @function TrackScrollbar */ pascal void TrackScrollbar( ControlRef control, short part ); /*! * @function BlinkInsertionPoint */ pascal void BlinkInsertionPoint( EventLoopTimerRef inTimer, void *inUserData ); #endif \ No newline at end of file diff --git a/Hex Editor/Classes/HexWindow.cpp b/Hex Editor/Classes/HexWindow.cpp new file mode 100644 index 0000000..dfc835e --- /dev/null +++ b/Hex Editor/Classes/HexWindow.cpp @@ -0,0 +1,409 @@ +#include "HexWindow.h" +#include "Events.h" +#include "stdio.h" +// #include "strings.h" + +extern globals g; +extern prefs p; + +/*** CREATOR ***/ +HexWindow::HexWindow( WindowRef newWindow ) +{ + // set contents to zero + memset( this, 0, sizeof(HexWindow) ); + + // initalise the bits that need initalising + window = newWindow; +#if USE_NIBS + // load stuff from nib file +#elif TARGET_API_MAC_CARBON + // create root control + ControlRef root; + CreateRootControl( window, &root ); + + // create header + header = null; + left = null; + right = null; + + // create scroll bar + Rect windowBounds; + ControlActionUPP liveTrackingProc = NewControlActionUPP( TrackScrollbar ); + GetWindowPortBounds( window, &windowBounds ); + windowBounds.top += kHeaderHeight -1; + windowBounds.bottom -= kScrollBarWidth -2; + windowBounds.left = windowBounds.right - kScrollBarWidth +1; + windowBounds.right += 1; + CreateScrollBarControl( window, &windowBounds, 0, 0, 0, 0, true, liveTrackingProc, &scrollbar ); + + ControlID id = { kScrollbarSignature, 0 }; + SetControlID( scrollbar, &id ); +#else + // only create a scroll bar + scrollbar = GetNewControl( kSystem7ScrollBarControl, window ); +#endif +} + +/*** DESTRUCTOR ***/ +HexWindow::~HexWindow( void ) +{ +// Host_ReleaseResourceData( hexInfo->data ); // decreases refCount + RemoveEventLoopTimer( timer ); + DisposeControl( scrollbar ); +} + + /**********/ + /* EVENTS */ +/**********/ + +/*** BOUNDS CHANGING ***/ +OSStatus HexWindow::BoundsChanging( EventRef event ) +{ + OSStatus error = noErr; +#if TARGET_API_MAC_CARBON + // check that window is not just being dragged + UInt32 attributes; + error = GetEventParameter( event, kEventParamAttributes, typeUInt32, null, sizeof(UInt32), null, &attributes ); + if( error || attributes & kWindowBoundsChangeUserDrag ) return eventNotHandledErr; + + // get new bounds + Rect windowBounds; + error = GetEventParameter( event, kEventParamCurrentBounds, typeQDRectangle, null, sizeof(Rect), null, &windowBounds ); + if( error ) return eventNotHandledErr; + + // constrain window width + UInt8 modulo = (windowBounds.right - windowBounds.left - 13*kHexCharWidth - kScrollBarWidth) % (4*kDataBlockWidth); + if( modulo < (2*kDataBlockWidth) ) windowBounds.right -= modulo; + else windowBounds.right += (4*kDataBlockWidth) - modulo; + + // constrain window height + modulo = (windowBounds.bottom - windowBounds.top - kHeaderHeight - 2*kTextMargin -4) % kHexLineHeight; + if( modulo < (kHexLineHeight/2) ) windowBounds.bottom -= modulo; + else windowBounds.bottom += kHexLineHeight - modulo; + + // update window rect to constrained version + if( (windowBounds.bottom - windowBounds.top) < kMinimumWindowHeight ) windowBounds.bottom = windowBounds.top + kMinimumWindowHeight; + if( (windowBounds.right - windowBounds.left) < kMinimumWindowWidth ) windowBounds.right = windowBounds.left + kMinimumWindowWidth; + error = SetEventParameter( event, kEventParamCurrentBounds, typeQDRectangle, sizeof(Rect), &windowBounds ); + if( error ) error = eventNotHandledErr; + if( g.systemVersion < kMacOSX ) return noErr; + error = BoundsChanged( event ); +#endif + return error; +} + +/*** BOUNDS CHANGED ***/ +OSStatus HexWindow::BoundsChanged( EventRef event ) +{ + Rect windowBounds; + OSStatus error = GetEventParameter( event, kEventParamCurrentBounds, typeQDRectangle, null, sizeof(Rect), null, &windowBounds ); + if( error ) return eventNotHandledErr; + +#if TARGET_API_MAC_CARBON + // resize header + SizeControl( header, (windowBounds.right - windowBounds.left) +2, kHeaderHeight +1 ); + SizeControl( left, (windowBounds.right - windowBounds.left) /2 -4, kHeaderHeight -4 ); + SizeControl( right, (windowBounds.right - windowBounds.left) /2 -4, kHeaderHeight -4 ); + MoveControl( right, (windowBounds.right - windowBounds.left) /2, 2 ); +#endif + + // resize scrollbar + MoveControl( scrollbar, (windowBounds.right - windowBounds.left) - kScrollBarWidth +1, kHeaderHeight -1 ); + SizeControl( scrollbar, kScrollBarWidth, (windowBounds.bottom - windowBounds.top) - kHeaderHeight - kScrollBarWidth +3 ); + + // calculate new scrollbar values & redraw window + UpdateHexInfo(); + InvalidateWindowRect( window, &windowBounds ); + + EventRef updateEvent; + error = CreateEvent( null, kEventClassWindow, kEventWindowUpdate, GetCurrentEventTime(), 0, &updateEvent ); + if( error == noErr ) + { + SetEventParameter( updateEvent, kEventParamDirectObject, typeWindowRef, sizeof(WindowRef), &window ); + SendEventToWindow( updateEvent, window ); + ReleaseEvent( updateEvent ); + } + return noErr; +} + +/*** CONTENT CLICK ***/ +OSStatus HexWindow::ContentClick( EventRef event ) +{ + #pragma unused( event ) + return eventNotHandledErr; +} + +/*** DRAW CONTENT ***/ +OSStatus HexWindow::DrawContent( EventRef event ) +{ + #pragma unused( event ) // could be null if I called this directly, therefore I ignore the param + + // set window + GrafPtr oldPort; + GetPort( &oldPort ); + SetPortWindowPort( window ); + + // initalise rects + RGBColour colour; + Rect windowBounds, drawingBounds; + GetWindowPortBounds( window, &windowBounds ); + SetRect( &drawingBounds, windowBounds.left, windowBounds.top + kHeaderHeight, windowBounds.right - kScrollBarWidth +1, windowBounds.bottom ); + SetRect( &hexRect, kHexCharWidth *11 -3, kTextMargin + kHeaderHeight, kHexCharWidth *58 +3, windowBounds.bottom - kTextMargin ); + SetRect( &asciiRect, kHexCharWidth *60 -3, kTextMargin + kHeaderHeight, kHexCharWidth *76 +3, windowBounds.bottom - kTextMargin ); + +#if Use_GWorlds + CGrafPtr origPort; + GDHandle origDev; + GWorldPtr theGWorld; + PixMapHandle thePixMap; + + // create GWorld + GetGWorld( &origPort, &origDev ); + // ( storage, bit depth, rect, colour table, GDevice, option flags ) // bit depth 0 = screen depth + NewGWorld( &theGWorld, p.GWorldDepth, &drawingBounds, null, null, 0 ); + if( !theGWorld ) + { + Host_DebugError( "\pNewGWorld() failed", 0 ); + return eventNotHandledErr; + } + SetGWorld( theGWorld, null ); + thePixMap = GetGWorldPixMap( theGWorld ); + + // lock the pixelmap in place + Boolean pixelsLocked = LockPixels( thePixMap ); + if( !pixelsLocked ) + { + UpdateGWorld( &theGWorld, 0, &drawingBounds, null, null, 0 ); + pixelsLocked = LockPixels( thePixMap ); + if( !pixelsLocked ) // if I still can't lock the damn pixels, give up! (don't even attempt writing to a moving target :) + { + SetGWorld( origPort, origDev ); + DisposeGWorld( theGWorld ); + Host_DebugError( "\pUpdateGWorld() failed", 0 ); + return eventNotHandledErr; + } + } +#endif + + // set font + short font; + GetFNum( "\pCourier", &font ); + TextFont( font ); + TextFace( 0 ); + TextSize( 12 ); + PenNormal(); + + // clear the background + RGBBackColor( &g.bgColour ); + EraseRect( &drawingBounds ); + + // set text colour + if( activeWindow ) + colour = g.black; + else colour = g.textColour; + RGBForeColor( &colour ); + + // draw box around hex + RGBBackColor( &g.white ); + EraseRect( &hexRect ); + FrameRect( &hexRect ); + if( activeWindow && p.GWorldDepth > 1 ) + { + RGBForeColor( &g.white ); + MoveTo( hexRect.left, hexRect.bottom ); + LineTo( hexRect.right, hexRect.bottom ); + LineTo( hexRect.right, hexRect.top ); + RGBForeColor( &g.bevelColour ); + MoveTo( hexRect.right, hexRect.top -1 ); + LineTo( hexRect.left -1, hexRect.top -1 ); + LineTo( hexRect.left -1, hexRect.bottom ); + } + + // draw box around ascii + RGBForeColor( &colour ); + RGBBackColor( &g.white ); + EraseRect( &asciiRect ); + FrameRect( &asciiRect ); + if( activeWindow && p.GWorldDepth > 1 ) + { + RGBForeColor( &g.white ); + MoveTo( asciiRect.left, asciiRect.bottom ); + LineTo( asciiRect.right, asciiRect.bottom ); + LineTo( asciiRect.right, asciiRect.top ); + RGBForeColor( &g.bevelColour ); + MoveTo( asciiRect.right, asciiRect.top -1 ); + LineTo( asciiRect.left -1, asciiRect.top -1 ); + LineTo( asciiRect.left -1, asciiRect.bottom ); + } + + // hilight where selected text will be + if( selStart != selEnd ) + { + // get regions of selected text + RgnHandle hexRgn = NewRgn(), asciiRgn = NewRgn(), insetRgn = NewRgn(); + FindSelectedRegions( window, hexRgn, asciiRgn ); + + // set foreground colour to hilight colour + RGBColour hilightColour; + GetPortHilightColour( GetWindowPort(window), &hilightColour ); + RGBForeColor( &hilightColour ); + + // draw what needs to be drawn + PenSize( 2, 2 ); + if( editingHex ) PaintRgn( hexRgn ); + else FrameRgn( hexRgn ); + if( !editingHex ) PaintRgn( asciiRgn ); + else FrameRgn( asciiRgn ); + PenNormal(); + + DisposeRgn( hexRgn ); + DisposeRgn( asciiRgn ); + DisposeRgn( insetRgn ); + } + + // reset text colour + if( activeWindow ) + colour = g.black; + else colour = g.textColour; + RGBForeColor( &colour ); + + // get useful data + Plug_WindowRef plugWindow = Host_GetPlugWindowFromWindowRef( window ); + Plug_ResourceRef resource = Host_GetTargetResource( plugWindow ); + + // get resource length & lock data handle + UInt32 length = Host_GetResourceSize( resource ); // which of these two would be faster? +// UInt32 length = GetHandleSize( data ); // the host callback returns a value from a struct, GetHandleSize probably does the same + HLock( data ); + if( Host_GetResourceSize( resource ) != GetHandleSize( data ) ) + { + SetGWorld( origPort, origDev ); + Host_DebugError( "\presource size != handle size", Host_GetResourceSize( resource ) ); // would be nice if I could pass both values! (any one know how to convert a number to > 1? - i.e. use "12.34" to pass 12 and 34) + SetGWorld( theGWorld, null ); + } + + // init variables + char buffer[16*4 +11]; + long addr; // offset of byte in current res chunk + UInt32 line, // current line number + currentByte = topline *16; // offset of byte in resource + short hexPos, asciiPos; + unsigned char ascii, hex1, hex2; // HexEdit uses 'register short' not 'unsigned char' ?!?! + + // start line count at scroll value + for( line = topline; line <= (lastline < bottomline? lastline:bottomline); line++ ) + { + hexPos = 10; + asciiPos = 59; + sprintf( buffer, "%08lX: ", currentByte ); + + // draw bytes + for( addr = 0; addr < 16; addr++ ) + { + if( currentByte < length ) + { + BlockMoveData( *data + currentByte, &ascii, 1 ); + hex1 = ascii; + hex2 = ascii; + hex1 >>= 4; + hex1 &= 0x0F; + hex2 &= 0x0F; + hex1 += (hex1 < 10)? 0x30 : 0x37; + hex2 += (hex2 < 10)? 0x30 : 0x37; + + buffer[hexPos++] = hex1; + buffer[hexPos++] = hex2; + buffer[hexPos++] = 0x20; + if( ascii >= p.lowChar && ascii < p.highChar ) + buffer[asciiPos++] = ascii; + else buffer[asciiPos++] = 0x2E; // full stop + } + else + { + // without this the ascii would be printed right next to the hex on the last line + buffer[hexPos++] = 0x20; + buffer[hexPos++] = 0x20; + buffer[hexPos++] = 0x20; + buffer[asciiPos++] = 0x20; + } + + // fill hex/ascii gap with a space to make it print + buffer[58] = 0x20; + + // advance current byte + currentByte++; + } + + MoveTo( kHexCharWidth, kHeaderHeight + kTextMargin + kHexLineHeight*(line - topline +1) ); + DrawText( buffer, 0, 75 ); // buffer, first byte, byte count + } + + // unlock handle and record window heights + HUnlock( data ); + +#if Use_GWorlds + SetGWorld( origPort, origDev ); + RGBBackColor( &g.white ); + RGBForeColor( &g.black ); + CopyBits( GetPortBitMapForCopyBits(theGWorld), GetPortBitMapForCopyBits(GetWindowPort(window)), &drawingBounds /*source rect*/, &drawingBounds /*dest rect*/, srcCopy, null ); + UnlockPixels( thePixMap ); // line redundant as GWorld is disposed of next! + DisposeGWorld( theGWorld ); +#endif + + // restore old port + SetPort( oldPort ); + return eventNotHandledErr; +} + + /****************/ + /* MAINTAINANCE */ +/****************/ + +/*** UPDATE HEX INFO ***/ +OSStatus HexWindow::UpdateHexInfo( void ) +{ + // hex and ascii rects + Rect windowBounds; + GetWindowPortBounds( window, &windowBounds ); + SetRect( &hexRect, kHexCharWidth *11 -3, kTextMargin + kHeaderHeight, kHexCharWidth *58 +3, windowBounds.bottom - kTextMargin ); + SetRect( &asciiRect, kHexCharWidth *60 -3, kTextMargin + kHeaderHeight, kHexCharWidth *76 +3, windowBounds.bottom - kTextMargin ); + + // scrollbar globals + firstline = 0; + topline = GetControlValue( scrollbar ); + bottomline = ((windowBounds.bottom - windowBounds.top - kHeaderHeight - 2*kTextMargin -4) / kHexLineHeight) + topline -1; + lastline = (GetHandleSize( data ) /16) - (GetHandleSize( data ) %16? 0:1); + + // scrollbar values + SInt32 number = lastline - (bottomline - topline); + if( number <= 0 ) SetControlValue( scrollbar, 0 ); + SetControlMaximum( scrollbar, (number > 0)? number:0 ); + SetControlViewSize( scrollbar, bottomline - topline ); + + return noErr; +} + +/*** INSERT BYTES ***/ +Boolean HexWindow::InsertBytes( void *newData, signed long length, unsigned long offset ) +{ + signed long size = (signed long) GetHandleSize( data ); + if( size + length <= 0 ) length = -size; // don't dispose of resource, just set it to zero length + + SInt8 state = HGetState( data ); + HLock( data ); + if( length > 0 ) // writing things + { + SetHandleSize( data, size + length ); + if( MemError() ) return false; + BlockMoveData( *data + offset, *data + offset + length, size - offset ); + BlockMoveData( newData, *data + offset, length ); + } + else if( length < 0 ) // deleting things + { + BlockMoveData( *data + offset, *data + offset + length, size - offset ); + SetHandleSize( data, size + length ); + if( MemError() ) return false; + } + HSetState( data, state ); + return true; +} \ No newline at end of file diff --git a/Hex Editor/Classes/HexWindow.h b/Hex Editor/Classes/HexWindow.h new file mode 100644 index 0000000..0d21746 --- /dev/null +++ b/Hex Editor/Classes/HexWindow.h @@ -0,0 +1 @@ +#include "Hex Editor.h" /*! * @header HexWindow * @discussion This is my data structure for saving all data which pertains to one editing session. Each window has one and only one associated HexWindow class. */ /*! * @class HexWindow * @abstract Stors data pertinanet to a window. * @discussion This class is stored in the refcon of the window, and contains all the methods necessary to maintain the window's groovy looks :-) */ typedef class HexWindow { /*! @var window Stores the Mac OS WindowRef pertaining to this window. */ WindowRef window; /*! @var header Header control (the grey bar at the top of the window). */ ControlRef header; /*! @var left Left hand side static text control (in the header). */ ControlRef left; /*! @var right Right hand side static text control (in the header). */ ControlRef right; /*! @var scrollbar Scroll bar down the right hand side */ ControlRef scrollbar; #if !TARGET_API_MAC_CARBON /*! @var themeSavvy True if this window is using an Appearance Manager WDEF. (Also used to determine if Appearance controls should be drawn if this window.) */ Boolean themeSavvy; #endif public: // all these variables should be private // non-controls version /*! @var hexRect The text well in which hexadecimal is drawn */ Rect hexRect; /*! @var asciiRect The text well in which ASCII is drawn */ Rect asciiRect; /*! @var selStart offset of first char of selection */ UInt32 selStart; /*! @var selEnd if selEnd == selStart, no selection exists */ UInt32 selEnd; /*! @var timer blinks the insertion point */ EventLoopTimerRef timer; Handle data; Boolean editingHex; // currently editing in hexadecimal? Boolean editedHigh; // already typed part of this char Boolean activeWindow; // is this window the user focus? Boolean insertionPointVisable; SInt8 hexChar; // the data currently being typed UInt32 firstline; // first line of valid data UInt32 lastline; // last line of valid data UInt32 topline; // line at top of screen UInt32 bottomline; // line at bottom of screen public: // methods HexWindow( WindowRef newWindow = null ); ~HexWindow( void ); // events /*! * @function BoundsChanging */ OSStatus BoundsChanging( EventRef event ); /*! * @function BoundsChanged */ OSStatus BoundsChanged( EventRef event ); /*! * @function ContentClick */ OSStatus ContentClick( EventRef event ); /*! * @function DrawContent */ OSStatus DrawContent( EventRef event = null ); /*! * @function UpdateHexInfo */ OSStatus UpdateHexInfo( void ); /*! * @function InsertBytes */ Boolean InsertBytes( void *data, signed long length, unsigned long offset ); friend OSStatus Plug_InitInstance( Plug_PlugInRef plug, Plug_ResourceRef resource ); } HexWindow, *HexWindowPtr; \ No newline at end of file diff --git a/Hex Editor/Classes/Initalisation.cpp b/Hex Editor/Classes/Initalisation.cpp new file mode 100644 index 0000000..6e59f3b --- /dev/null +++ b/Hex Editor/Classes/Initalisation.cpp @@ -0,0 +1 @@ +#include "Initalisation.h" #include "HexWindow.h" #include "Events.h" #include "Utility.h" globals g; prefs p; /************************/ /* EDITOR INITALIZATION */ /************************/ /*** INITALISE GLOBALS ***/ OSStatus InitGlobals( void ) { // get system version OSStatus error = Gestalt( gestaltSystemVersion, &g.systemVersion ); if( error ) return error; // set up 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 ); // check appearance availablilty #if TARGET_API_MAC_CARBON g.useAppearance = true; #else ProcessSerialNumber psn; error = GetCurrentProcess( &psn ); if( error ) g.useAppearance = false; else g.useAppearance = IsAppearanceClient( &psn ); #endif // initalise preferences p.version = kHexEditorCurrentVersion; p.lowChar = 0x20; p.highChar = 0x7F; p.GWorldDepth = 8; return error; } /*** INITALISE NEW EDITOR INSTANCE ***/ OSStatus Plug_InitInstance( Plug_PlugInRef plug, Plug_ResourceRef resource ) { WindowRef window; OSStatus error = InitGlobals(); if( error ) return error; #if USE_NIBS // create a nib reference (only searches the application bundle) IBNibRef nibRef = null; error = CreateNibReference( CFSTR("Hex Editor"), &nibRef ); if( error != noErr || nibRef == null ) { // Host_DisplayError( "\pThe nib file reference could not be obtained.", "\p", 0 ); return error; } // create window error = CreateWindowFromNib( nibRef, CFSTR("Hex Window"), &window ); if( error != noErr || window == null ) { // Host_DisplayError( "\pA file window could not be obtained from the nib file.", "\p", 0 ); return error; } // dispose of nib ref DisposeNibReference( nibRef ); #elif TARGET_API_MAC_CARBON // create window Rect creationBounds; SetRect( &creationBounds, 0, 0, kDefaultWindowWidth, kDefaultWindowHeight ); OffsetRect( &creationBounds, 8, 48 ); WindowAttributes attributes = kWindowStandardDocumentAttributes | kWindowStandardHandlerAttribute | kWindowInWindowMenuAttribute; if( g.systemVersion >= kMacOSX ) attributes |= kWindowLiveResizeAttribute; error = CreateNewWindow( kDocumentWindowClass, attributes, &creationBounds, &window ); #else /* if( g.useAppearance && g.systemVersion >= kMacOSEight ) { window = GetNewCWindow( kFileWindow8, null, kFirstWindowOfClass ); themeSavvy = true; } else { */ window = GetNewCWindow( kFileWindow7, null, kFirstWindowOfClass ); DrawGrowIcon( window ); /* themeSavvy = false; } */ SizeWindow( window, kDefaultWindowWidth, kDefaultWindowHeight, false ); #endif // register mac window with host and retreive plug window Plug_WindowRef plugWindow = Host_RegisterWindow( plug, resource, window ); // cerate new hex window class HexWindowPtr hexWindow = new HexWindow( window ); // set window refCon to my class Host_SetWindowRefCon( plugWindow, (UInt32) hexWindow ); // load resource data into handle hexWindow->data = Host_GetResourceData( resource ); // handle disposed of by calling Host_ReleaseResData() // window is not yet active, will receive activate event soon hexWindow->activeWindow = false; // set window's background to default for theme if( g.useAppearance ) SetThemeWindowBackground( window, kThemeBrushModelessDialogBackgroundActive, false ); #if TARGET_API_MAC_CARBON // install window event handler EventHandlerRef ref = null; EventHandlerUPP handler = NewEventHandlerUPP( CarbonWindowEventHandler ); EventTypeSpec events[] = { { kEventClassWindow, kEventWindowClose }, { kEventClassWindow, kEventWindowActivated }, { kEventClassWindow, kEventWindowDeactivated }, { kEventClassWindow, kEventWindowBoundsChanging }, { kEventClassWindow, kEventWindowBoundsChanged }, { kEventClassWindow, kEventWindowHandleContentClick }, { kEventClassWindow, kEventWindowDrawContent }, { kEventClassKeyboard, kEventRawKeyDown }, { kEventClassKeyboard, kEventRawKeyRepeat } }; InstallWindowEventHandler( window, handler, GetEventTypeCount(events), events, plug, &ref ); // install HI event handler ref = null; handler = NewEventHandlerUPP( CarbonHIEventHandler ); EventTypeSpec HIevents[] = { { kEventClassMenu, kEventMenuEnableItems }, { kEventClassCommand, kEventCommandProcess } }; InstallWindowEventHandler( window, handler, GetEventTypeCount(HIevents), HIevents, null, &ref ); #else ClassicEventHandlerUPP handler = NewClassicEventHandlerUPP( ClassicWindowEventHandler ); Host_InstallClassicWindowEventHandler( plugWindow, handler ); #endif // get window rect Rect windowBounds; GetWindowPortBounds( window, &windowBounds ); #if USE_NIBS // get text edit controls which were created from the nib #elif TARGET_API_MAC_CARBON // create header ControlID id; Rect bounds = windowBounds; InsetRect( &bounds, -1, -1 ); bounds.bottom = bounds.top + kHeaderHeight +1; CreateWindowHeaderControl( window, &bounds, false, &hexWindow->header ); id.id = 0; id.signature = kHeaderSignature; SetControlID( hexWindow->header, &id ); // set up header font information ControlFontStyleRec fontStyle = {}; fontStyle.flags = kControlUseFontMask + kControlUseJustMask; fontStyle.font = kControlFontSmallSystemFont; fontStyle.just = teJustLeft; // create header static text controls GetWindowPortBounds( window, &windowBounds ); SetRect( &bounds, windowBounds.left +4, 2, (windowBounds.right - windowBounds.left) /2, kHeaderHeight -2 ); CreateStaticTextControl( window, &bounds, CFSTR("left side"), &fontStyle, &hexWindow->left ); id.id = 0; id.signature = kLeftTextSignature; SetControlID( hexWindow->left, &id ); fontStyle.just = teJustRight; SetRect( &bounds, (windowBounds.right - windowBounds.left) /2, 2, windowBounds.right -4, kHeaderHeight -2 ); CreateStaticTextControl( window, &bounds, CFSTR("right side"), &fontStyle, &hexWindow->right ); id.id = 0; id.signature = kRightTextSignature; SetControlID( hexWindow->right, &id ); // SetHeaderText(); // embed text controls within header EmbedControl( hexWindow->left, hexWindow->header ); EmbedControl( hexWindow->right, hexWindow->header ); #else // only create a scroll bar, draw everything else DrawWindow( window ); #endif #if TARGET_API_MAC_CARBON // install blinking timer EventLoopTimerUPP timerProc = NewEventLoopTimerUPP( BlinkInsertionPoint ); InstallEventLoopTimer( GetMainEventLoop(), 0, kEventDurationSecond /3, timerProc, window, &hexWindow->timer ); #endif // set scrollbar globals hexWindow->UpdateHexInfo(); // add menu to menu bar Host_AppendMenuToBar( plug, kEditorMenu ); // show window ShowWindow( window ); // this is the plug's responsibility SelectWindow( window ); BringToFront( window ); return error; } \ No newline at end of file diff --git a/Hex Editor/Classes/Initalisation.h b/Hex Editor/Classes/Initalisation.h new file mode 100644 index 0000000..1f631eb --- /dev/null +++ b/Hex Editor/Classes/Initalisation.h @@ -0,0 +1 @@ +#include "Hex Editor.h" #ifndef _ResKnife_HexEditor_Initalisation_ #define _ResKnife_HexEditor_Initalisation_ /*! * @function InitGlobals */ OSStatus InitGlobals( void ); #endif \ No newline at end of file diff --git a/Hex Editor/Classes/Utility.cpp b/Hex Editor/Classes/Utility.cpp new file mode 100644 index 0000000..94aa406 --- /dev/null +++ b/Hex Editor/Classes/Utility.cpp @@ -0,0 +1,233 @@ +#include "Utility.h" + + /**********************/ + /* QUICKDRAW ROUTINES */ +/**********************/ + +/*** SET COLOUR ***/ +void SetColour( RGBColor *colour, UInt16 red, UInt16 green, UInt16 blue ) +{ + colour->red = red; + colour->green = green; + colour->blue = blue; +} + +/*** MAKE LOCAL ***/ +void MakeLocal( WindowRef window, Point globalPoint, Point *localPoint ) +{ + GrafPtr oldPort; + GetPort( &oldPort ); + SetPortWindowPort( window ); + + localPoint->h = globalPoint.h; + localPoint->v = globalPoint.v; + GlobalToLocal( localPoint ); + + SetPort( oldPort ); +} + +/*** MAKE GLOBAL ***/ +void MakeGlobal( WindowRef window, Point localPoint, Point *globalPoint ) +{ + GrafPtr oldPort; + GetPort( &oldPort ); + SetPortWindowPort( window ); + + globalPoint->h = localPoint.h; + globalPoint->v = localPoint.v; + LocalToGlobal( globalPoint ); + + SetPort( oldPort ); +} + + /*****************************/ + /* HEX <==> ASCII CONVERSION */ +/*****************************/ + +/*** ASCII TO TEXT ***/ +void AsciiToText( char *source, char *dest, unsigned long size ) +{ + char ascii = 0x00, text = 0x00; + unsigned long sourceOffset = 0, destOffset = 0; + while( sourceOffset < size ) + { + ascii = *(source + sourceOffset); + if( ascii < 0x20 || ascii >= 0x7F ) text = (char) 0x2E; // full stop + else if( ascii == 0x20 ) text = (char) 0xCA; // nbsp + else text = ascii; + *(dest + destOffset++) = text; + sourceOffset++; + } +} + +/*** ASCII TO HEX ***/ +void AsciiToHex( char *source, char *dest, unsigned long size ) +{ + char hex1 = 0x00, hex2 = 0x00; + unsigned long sourceOffset = 0, destOffset = 0; + while( sourceOffset < size ) + { + hex1 = *(source + sourceOffset); + hex2 = *(source + sourceOffset); + hex1 >>= 4; + hex1 &= 0x0F; + hex2 &= 0x0F; + hex1 += (hex1 < 10)? 0x30 : 0x37; + hex2 += (hex2 < 10)? 0x30 : 0x37; + + *(dest + destOffset++) = hex1; + *(dest + destOffset++) = hex2; + *(dest + destOffset++) = 0x20; + sourceOffset++; + } +} + +/*** HEX TO ASCII ***/ +void HexToAscii( char *source, char *dest, unsigned long size ) +{ + char currentByte = 0x00, newByte = 0x00, tempByte = 0x00; + unsigned long sourceOffset = 0, destOffset = 0; + while( sourceOffset < size ) + { + currentByte = *(source + sourceOffset); + if( currentByte >= 0x30 && currentByte <= 0x39 ) newByte = currentByte - 0x30; // 0 to 9 + else if( currentByte >= 0x41 && currentByte <= 0x46 ) newByte = currentByte - 0x37; // A to F + else if( currentByte >= 0x61 && currentByte <= 0x66 ) newByte = currentByte - 0x57; // a to f + else newByte = 0x00; + newByte <<= 4; + currentByte = *(source + sourceOffset +1); + if( currentByte >= 0x30 && currentByte <= 0x39 ) tempByte = currentByte - 0x30; // 0 to 9 + else if( currentByte >= 0x41 && currentByte <= 0x46 ) tempByte = currentByte - 0x37; // A to F + else if( currentByte >= 0x61 && currentByte <= 0x66 ) tempByte = currentByte - 0x57; // a to f + else tempByte = 0x00; + newByte += tempByte & 0x0F; + *(dest + destOffset++) = newByte; + sourceOffset += 3; + } +} + +/*** LONG TO HEX ***/ +void LongToHex( char *source, char *dest ) +{ + // copy of AsciiToHex but with changes as noted + char hex1 = 0x00, hex2 = 0x00; + unsigned long sourceOffset = 0, destOffset = 0; + while( sourceOffset < sizeof(unsigned long) ) // size is always four + { + hex1 = *(source + sourceOffset); + hex2 = *(source + sourceOffset); + hex1 >>= 4; + hex1 &= 0x0F; + hex2 &= 0x0F; + hex1 += (hex1 < 10)? 0x30 : 0x37; + hex2 += (hex2 < 10)? 0x30 : 0x37; + + *(dest + destOffset++) = hex1; + *(dest + destOffset++) = hex2; // no space inserted + sourceOffset++; + } +} + + /*******************/ + /* STRING ROUTINES */ +/*******************/ + +/*** C STRING LENGTH ***/ +unsigned long CStringLength( char *string ) +{ + unsigned long length; + Boolean end = false; + for( length = 0; end == false; length++ ) + if( *(string + length) == 0x00 ) end = true; + return length; +} + +/*** PASCAL STRING LENGTH ***/ +unsigned char PStringLength( unsigned char *string ) +{ + return *string; +} + +/*** TYPE TO C STRING ***/ +void TypeToCString( const OSType type, char *string ) +{ + BlockMoveData( &type, &string[0], sizeof(OSType) ); + string[sizeof(OSType)] = 0x00; +} + +/*** TYPE TO PASCAL STRING ***/ +void TypeToPString( const OSType type, Str255 string ) +{ + string[0] = sizeof(OSType); + BlockMoveData( &type, &string[1], sizeof(OSType) ); +} + +/*** TYPE TO CORE FOUNDATION STRING ***/ +void TypeToCFString( const OSType type, CFStringRef *string ) +{ + char cString[5]; + TypeToCString( type, (char *) &cString ); + *string = CFStringCreateWithCString( CFAllocatorGetDefault(), (char *) &cString, kCFStringEncodingMacRoman ); +} + +/*** COPY C STRING ***/ +void CopyCString( UInt8 *source, UInt8 *dest ) +{ +// #pragma warning off + while( *dest++ = *source++ ); +// #pragma warning reset +} + +/*** COPY PASCAL STRING ***/ +void CopyPString( UInt8 *source, UInt8 *dest ) +{ + UInt8 length = *source, count; + for( count = 0; count <= length; count++ ) + *dest++ = *source++; +} + +/*** EQUAL C STRINGS ***/ +Boolean EqualCStrings( UInt8 *source, UInt8 *dest ) +{ + while( *source != 0x00 ) + if( *source++ != *dest++ ) return false; + return true; +} + +/*** EQUAL PASCAL STRINGS ***/ +Boolean EqualPStrings( UInt8 *source, UInt8 *dest ) +{ + UInt8 length = *source, count; + for( count = 0; count <= length; count++ ) + if( *source++ != *dest++ ) return false; + return true; +} + +/*** APPEND ONE PASCAL STRING ONTO ANOTHER ***/ +void AppendPString( Str255 original, ConstStr255Param added ) +{ + short numberBytes = added[0]; // length of string to be added + short totalLength = added[0] + original[0]; + + if( totalLength > 255 ) // too long, adjust number of bytes to add + { + totalLength = 255; + numberBytes = totalLength - original[0]; + } + + BlockMoveData( &added[1], &original[totalLength-numberBytes + 1], numberBytes ); + original[0] = totalLength; // new length of original string + while( ++totalLength <= 255 ) + original[totalLength] = 0x00; // set rest of string to zero +} + + /*****************/ + /* MENU ROUTINES */ +/*****************/ + +/*** ENABLE MENU COMMAND ***/ +void EnableCommand( MenuRef menu, MenuCommand command, Boolean enable ) +{ + if( enable ) EnableMenuCommand( menu, command ); + else DisableMenuCommand( menu, command ); +} diff --git a/Hex Editor/Classes/Utility.h b/Hex Editor/Classes/Utility.h new file mode 100644 index 0000000..18dbaa1 --- /dev/null +++ b/Hex Editor/Classes/Utility.h @@ -0,0 +1,31 @@ +#include "Hex Editor.h" + +#ifndef _ResKnife_HexEditor_Utility_ +#define _ResKnife_HexEditor_Utility_ + +/* QuickDraw Routines */ +void SetColour( RGBColor *colour, UInt16 red, UInt16 green, UInt16 blue ); +void MakeLocal( WindowRef window, Point globalPoint, Point *localPoint ); +void MakeGlobal( WindowRef window, Point localPoint, Point *globalPoint ); + +/* ASCII <=> hex */ +void AsciiToText( char *source, char *dest, unsigned long size ); +void AsciiToHex( char *source, char *dest, unsigned long size ); +void HexToAscii( char *source, char *dest, unsigned long size ); +void LongToHex( char *source, char *dest ); + +/* strings */ +unsigned long CStringLength( char *string ); +unsigned char PStringLength( unsigned char *string ); +void TypeToCString( const OSType type, char *string ); +void TypeToPString( const OSType type, Str255 string ); +void TypeToCFString( const OSType type, CFStringRef *string ); +void CopyCString( UInt8 *source, UInt8 *dest ); +void CopyPString( UInt8 *source, UInt8 *dest ); +Boolean EqualCStrings( UInt8 *source, UInt8 *dest ); +Boolean EqualPStrings( UInt8 *source, UInt8 *dest ); +void AppendPString( Str255 original, ConstStr255Param added ); + +void EnableCommand( MenuRef menu, MenuCommand command, Boolean enable ); + +#endif \ No newline at end of file diff --git a/Hex Editor/Hex Editor.h b/Hex Editor/Hex Editor.h new file mode 100644 index 0000000..6abb385 --- /dev/null +++ b/Hex Editor/Hex Editor.h @@ -0,0 +1,149 @@ +#if !TARGET_API_MAC_OS8 + #include +#endif + +#ifndef _ResKnife_Plug_ + #define _ResKnife_Plug_ 1 + #include "HostCallbacks.h" +#endif + +#ifndef _ResKnife_HexEditor_ +#define _ResKnife_HexEditor_ + +// abbreviations +#define null NULL +#define Use_Nibs 0 +#define Use_GWorlds 1 + +// Easire constants +#define RGBColour RGBColor + +// Easier API call names +#define GetWindowRefCon( window ) (long) GetWRefCon( window ) +#define SetWindowRefCon( window, refcon ) SetWRefCon( window, refcon ) +#define GetWindowTitle( window, string ) GetWTitle( window, string ) +#define SetWindowTitle( window, name ) SetWTitle( window, name ) +#define InvalidateRect( bounds ) InvalRect( bounds ) +#define InvalidateWindowRect( window, bounds ) (OSStatus) InvalWindowRect( window, bounds ) +#define RectToRegion( region, rect ) RectRgn( region, rect ) +#define SetPoint( point, x, y ) SetPt( point, x, y ) +/* apperance.h */ +#define SetThemeTextColour( c, d, cd ) (OSStatus) SetThemeTextColor( c, d, cd ) +#define IsThemeInColour( d, cd ) (Boolean) IsThemeInColor( d, cd ) +#define GetThemeAccentColours( out ) (OSStatus) GetThemeAccentColors( out ) +#define SetThemeTextColourForWindow( w, a, d, cd ) (OSStatus) SetThemeTextColorForWindow( w, a, d, cd ) +#define GetThemeBrushAsColour( b, d, cd, out ) (OSStatus) GetThemeBrushAsColor( b, d, cd, out ) +#define GetThemeTextColour( c, d, cd, out) (OSStatus) GetThemeTextColor( c, d, cd, out) +/* some other file */ +#define HilightColour( colour ) HiliteColor( colour ) +#define GetPortHilightColour( window, colour ) GetPortHiliteColor( window, colour ) + +/* Global Variables */ +struct globals +{ + // application + Str255 fragName; + Str255 prefsName; + + // system info + SInt32 systemVersion; + Boolean dragAvailable; + Boolean translucentDrag; + Boolean navAvailable; + Boolean useAppearance; + + // colours + RGBColour white; // 0xFFFF, 65535 + RGBColour bgColour; // 0xEEEE, 61166 + RGBColour sortColour; // 0xDDDD, 56797 + RGBColour bevelColour; // 0xAAAA, 43690 + RGBColour textColour; // 0x7777, 30583 + RGBColour frameColour; // 0x5555, 21845 + RGBColour black; // 0x0000, 0 +}; + +/* Preferences */ +struct prefs +{ + UInt32 version; // == kHexEditorCurrentVersion, when saved to disk allows older prefs to be read in + + // highest and lowest chars displayed + UInt8 lowChar; + UInt8 highChar; + UInt8 GWorldDepth; +}; + +// MacOS versions +const SInt32 kMacOSSevenPointOne = 0x00000710; +const SInt32 kMacOSSevenPointFivePointFive = 0x00000755; +const SInt32 kMacOSEight = 0x00000800; +const SInt32 kMacOSEightPointFive = 0x00000850; +const SInt32 kMacOSEightPointSix = 0x00000860; +const SInt32 kMacOSNine = 0x00000900; +const SInt32 kMacOSNinePointOne = 0x00000910; +const SInt32 kMacOSTen = 0x00001000; + +const SInt32 kMacOS71 = kMacOSSevenPointOne; +const SInt32 kMacOS755 = kMacOSSevenPointFivePointFive; +const SInt32 kMacOS8 = kMacOSEight; +const SInt32 kMacOS85 = kMacOSEightPointFive; +const SInt32 kMacOS86 = kMacOSEightPointSix; +const SInt32 kMacOS9 = kMacOSNine; +const SInt32 kMacOS91 = kMacOSNinePointOne; +const SInt32 kMacOSX = kMacOSTen; + +/*** APPLE EVENT SUPPORT ***/ +const OSType kResEditorAEType = FOUR_CHAR_CODE('ResK'); +const OSType kResTransferType = FOUR_CHAR_CODE('rsrc'); + +enum DragReceiveType // copy & paste uses these too +{ + kScrapFlavorTypeHex = FOUR_CHAR_CODE('HEX '), + kScrapFlavorTypeResource = kResTransferType +}; + +/*** CONSTANTS ***/ +const UInt32 kHexEditorCurrentVersion = 0x00030003; +const UInt16 kHeaderHeight = 20; +const UInt16 kScrollBarWidth = 16; +const UInt16 kTextMargin = 5; // top & bottom margin for hand-drawn editing areas +const UInt16 kTextFrameBorder = 9; // border around controls +const UInt16 kTextInnerBuffer = 2; // border within controls +const UInt16 kHexCharWidth = 7; +const UInt16 kHexLineHeight = 11; +const UInt16 kDataBlockWidth = (8*kHexCharWidth); +const UInt16 kOffsetColumnWidth = (10*kHexCharWidth); // no need to consider border on leftmost edge of hex field +const UInt16 kMinimumWindowWidth = kOffsetColumnWidth + (4*kDataBlockWidth) + (3*kHexCharWidth) + kScrollBarWidth; +const UInt16 kDefaultWindowWidth = kMinimumWindowWidth + (4*kDataBlockWidth); +const UInt16 kMinimumWindowHeight = kHeaderHeight + (2*kTextMargin) + (10*kHexLineHeight) +4; +const UInt16 kDefaultWindowHeight = kMinimumWindowHeight + (6*kHexLineHeight); + +const UInt32 kHeaderSignature = FOUR_CHAR_CODE('head'); +const UInt32 kLeftTextSignature = FOUR_CHAR_CODE('left'); +const UInt32 kRightTextSignature = FOUR_CHAR_CODE('rght'); +const UInt32 kOffsetTextSignature = FOUR_CHAR_CODE('offs'); +const UInt32 kHexTextSignature = FOUR_CHAR_CODE('hex '); +const UInt32 kAsciiTextSignature = FOUR_CHAR_CODE('asci'); +const UInt32 kScrollbarSignature = FOUR_CHAR_CODE('scrl'); + +/* RESOURCES */ + +enum // menus +{ + kEditorMenu = 128 +}; + +enum // windows +{ + kFileWindow7 = 128, + kFileWindow8 = 129 +}; + +enum // controls +{ + kSystem7ScrollBarControl = 128, + kAppearanceScrollBarControl = 129, + kNormalHeaderControl = 130 +}; + +#endif diff --git a/PICT Editor/Classes/Initalisation.cpp b/PICT Editor/Classes/Initalisation.cpp new file mode 100644 index 0000000..ede5f5d --- /dev/null +++ b/PICT Editor/Classes/Initalisation.cpp @@ -0,0 +1 @@ +#include "Initalisation.h" globals g; /*** INITALISE NEW EDITOR INSTANCE ***/ OSStatus Plug_InitInstance( Plug_PlugInRef plug, Plug_ResourceRef resource ) { // get system version OSStatus error = Gestalt( gestaltSystemVersion, &g.systemVersion ); if( error ) return error; // create window Rect creationBounds; WindowRef window; SetRect( &creationBounds, 0, 0, kDefaultWindowWidth, kDefaultWindowHeight ); OffsetRect( &creationBounds, 8, 48 ); WindowAttributes attributes = kWindowCloseBoxAttribute | kWindowCollapseBoxAttribute | kWindowStandardHandlerAttribute | kWindowInWindowMenuAttribute; if( g.systemVersion >= kMacOSX ) attributes |= kWindowLiveResizeAttribute; error = CreateNewWindow( kDocumentWindowClass, attributes, &creationBounds, &window ); // register mac window with host and retreive plug window Plug_WindowRef plugWindow = Host_RegisterWindow( plug, resource, window ); // cerate new pict window class PictWindowPtr pictWindow = new PictWindow( window ); // set window refCon to my class Host_SetWindowRefCon( plugWindow, (UInt32) pictWindow ); // set the window pic to the data handle PicHandle picture = (PicHandle) Host_GetResourceData( resource ); if( picture == null ) Host_DisplayError( "\pNo picture could be obtained.", "\p", 0 ); // get pict rect PictInfo pictInfo; GetPictInfo( picture, &pictInfo, 0, 0, 0, 0 ); Rect pictRect; pictRect.top = 0; pictRect.left = 0; pictRect.right = pictInfo.sourceRect.right - pictInfo.sourceRect.left; pictRect.bottom = pictInfo.sourceRect.bottom - pictInfo.sourceRect.top; SizeWindow( window, pictRect.right, pictRect.bottom, false ); #if TARGET_API_MAC_CARBON ControlRef picControl; // this is better than SetWindowPic() in Carbon ControlButtonContentInfo content; content.contentType = kControlContentPictHandle; content.u.picture = picture; // bug: if handle dissapears, control is fucked CreatePictureControl( window, &pictRect, &content, true, &picControl ); // DrawOneControl( picControl ); // bug: work around for bug in ControlManager /* HLockHi( picture ); SetControlData( picControl, kControlPicturePart, kControlPictureHandleTag, sizeof(picture), *picture ); */ #else SetWindowPic( window, picture ); // this doesn't work properly on X #endif // show window ShowWindow( window ); SelectWindow( window ); return error; } \ No newline at end of file diff --git a/PICT Editor/Classes/Initalisation.h b/PICT Editor/Classes/Initalisation.h new file mode 100644 index 0000000..1afcbd4 --- /dev/null +++ b/PICT Editor/Classes/Initalisation.h @@ -0,0 +1 @@ +#include "PICT Editor.h" typedef class PictWindow PictWindow, *PictWindowPtr; /*** PICT WINDOW ***/ class PictWindow { WindowRef window; Rect rect; // size of picture, { 0, 0, x, y } public: PictWindow( WindowRef newWindow ); }; \ No newline at end of file diff --git a/PICT Editor/Classes/Parser.h b/PICT Editor/Classes/Parser.h new file mode 100644 index 0000000..e69de29 diff --git a/PICT Editor/Classes/PictWindow.cpp b/PICT Editor/Classes/PictWindow.cpp new file mode 100644 index 0000000..9ef5b21 --- /dev/null +++ b/PICT Editor/Classes/PictWindow.cpp @@ -0,0 +1,7 @@ +#include "Initalisation.h" + +/*** CONSTRUCTOR ***/ +PictWindow::PictWindow( WindowRef newWindow ) +{ + window = newWindow; +} \ No newline at end of file diff --git a/PICT Editor/PICT Editor.h b/PICT Editor/PICT Editor.h new file mode 100644 index 0000000..e6df902 --- /dev/null +++ b/PICT Editor/PICT Editor.h @@ -0,0 +1 @@ +#if !TARGET_API_MAC_OS8 #include #endif #ifndef _ResKnife_Plug_ #define _ResKnife_Plug_ 1 #include "HostCallbacks.h" #endif #ifndef _ResKnife_PictEditor_ #define _ResKnife_PictEditor_ // abbreviations #define null NULL #define Use_Nibs 0 #define Use_GWorlds 1 // Easier API call names #define GetWindowRefCon( window ) (long) GetWRefCon( window ) #define SetWindowRefCon( window, refcon ) SetWRefCon( window, refcon ) #define GetWindowTitle( window, string ) GetWTitle( window, string ) #define SetWindowTitle( window, name ) SetWTitle( window, name ) #define InvalidateRect( bounds ) InvalRect( bounds ) #define InvalidateWindowRect( window, bounds ) (OSStatus) InvalWindowRect( window, bounds ) #define RectToRegion( region, rect ) RectRgn( region, rect ) #define SetPoint( point, x, y ) SetPt( point, x, y ) #define HilightColour( colour ) HiliteColor( colour ) #define GetPortHilightColour( window, colour ) GetPortHiliteColor( window, colour ) /* Global Variables */ struct globals { // application Str255 fragName; Str255 prefsName; // system info SInt32 systemVersion; Boolean dragAvailable; Boolean translucentDrag; Boolean navAvailable; Boolean useAppearance; // colours RGBColor white; // 0xFFFF, 65535 RGBColor bgColour; // 0xEEEE, 61166 RGBColor black; // 0x0000, 0 }; /* Preferences */ struct prefs { UInt32 version; // == kPictEditorCurrentVersion, when saved to disk allows older prefs to be read in UInt8 GWorldDepth; }; /*** CONSTANTS ***/ const UInt32 kPictEditorCurrentVersion = 0x00030003; const UInt16 kHeaderHeight = 20; const UInt16 kScrollBarWidth = 16; const UInt16 kMinimumWindowWidth = 384; const UInt16 kDefaultWindowWidth = kMinimumWindowWidth; const UInt16 kMinimumWindowHeight = 256 + kHeaderHeight; const UInt16 kDefaultWindowHeight = kMinimumWindowHeight; const UInt32 kHeaderSignature = FOUR_CHAR_CODE('head'); const UInt32 kLeftTextSignature = FOUR_CHAR_CODE('left'); const UInt32 kRightTextSignature = FOUR_CHAR_CODE('rght'); const UInt32 kScrollbarSignature = FOUR_CHAR_CODE('scrl'); // MacOS versions const SInt32 kMacOSSevenPointOne = 0x00000710; const SInt32 kMacOSSevenPointFivePointFive = 0x00000755; const SInt32 kMacOSEight = 0x00000800; const SInt32 kMacOSEightPointFive = 0x00000850; const SInt32 kMacOSEightPointSix = 0x00000860; const SInt32 kMacOSNine = 0x00000900; const SInt32 kMacOSNinePointOne = 0x00000910; const SInt32 kMacOSTen = 0x00001000; const SInt32 kMacOS71 = kMacOSSevenPointOne; const SInt32 kMacOS755 = kMacOSSevenPointFivePointFive; const SInt32 kMacOS8 = kMacOSEight; const SInt32 kMacOS85 = kMacOSEightPointFive; const SInt32 kMacOS86 = kMacOSEightPointSix; const SInt32 kMacOS9 = kMacOSNine; const SInt32 kMacOS91 = kMacOSNinePointOne; const SInt32 kMacOSX = kMacOSTen; /* RESOURCES */ enum // menus { kEditorMenu = 128 }; enum // windows { kFileWindow7 = 128, kFileWindow8 = 129 }; enum // controls { kSystem7ScrollBarControl = 128, kAppearanceScrollBarControl = 129, kNormalHeaderControl = 130 }; #endif \ No newline at end of file diff --git a/Prefix Files/Carbon Prefix.h b/Prefix Files/Carbon Prefix.h new file mode 100644 index 0000000..2656210 --- /dev/null +++ b/Prefix Files/Carbon Prefix.h @@ -0,0 +1 @@ +// include file #define CALL_NOT_IN_CARBON 0 #define TARGET_API_MAC_OS8 1 #define TARGET_API_MAC_CARBON 1 #define TARGET_API_MAC_OSX 0 #define OPAQUE_UPP_TYPES 1 #define OPAQUE_TOOLBOX_STRUCTS 1 #define ACCESSOR_CALLS_ARE_FUNCTIONS 1 #include \ No newline at end of file diff --git a/Prefix Files/Classic Prefix.h b/Prefix Files/Classic Prefix.h new file mode 100644 index 0000000..c305ed0 --- /dev/null +++ b/Prefix Files/Classic Prefix.h @@ -0,0 +1 @@ +// include file #define CALL_NOT_IN_CARBON 1 #define TARGET_API_MAC_OS8 1 #define TARGET_API_MAC_CARBON 0 #define TARGET_API_MAC_OSX 0 #define OPAQUE_UPP_TYPES 0 #define OPAQUE_TOOLBOX_STRUCTS 0 #define ACCESSOR_CALLS_ARE_FUNCTIONS 1 // #include // #include // #include // #include // #include // #include #include #include // #include // #include // #include // #include // #include // #include // #include // #include #include // #include // #include // #include // #include #include /* New for 3.3.1 */ #include // #include #include // #include // #include // #include #include // #include // #include // #include #include #include // #include // #include // #include // #include #include // #include #include // #include #include // #include #include // #include // #include // #include #include /* New for 3.3.1 */ // #include /* New for 3.3.1 */ #include #include #include #include // #include // #include // #include #include /* New for 3.2 */ // #include // #include // #include // #include // #include // #include // #include // #include // #include // #include // #include #include #include // #include #include #include // #include // #include #include #include // #include // #include // #include #include // #include // #include #include // #include // #include // #include // #include /* New for 3.3.1 */ // #include \ No newline at end of file diff --git a/ResKnife.mcp b/ResKnife.mcp new file mode 100644 index 0000000000000000000000000000000000000000..6f2fbf1c43ab77bda975d6701bc5d97980df5d24 GIT binary patch literal 291585 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 rj{Z@~B|FM?i@yXf6_3V9|M};@`w{;S0LTW7 literal 0 HcmV?d00001 diff --git a/ResKnife.pbproj/project.pbxproj b/ResKnife.pbproj/project.pbxproj new file mode 100644 index 0000000..ad17213 --- /dev/null +++ b/ResKnife.pbproj/project.pbxproj @@ -0,0 +1,2133 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 32; + objects = { + F5730B930159528A01000001 = { + isa = PBXFileReference; + path = defaults.plist; + refType = 4; + }; + F5730B940159528A01000001 = { + fileRef = F5730B930159528A01000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B5880F0156D2A601000001 = { + buildStyles = ( + F5EBF6B801573EC201000001, + F5EBF6B901573EC201000001, + ); + isa = PBXProject; + mainGroup = F5B588100156D2A601000001; + productRefGroup = F5B588110156D30301000001; + projectDirPath = ""; + targets = ( + F5B588130156D30301000001, + F5B588750156D5CB01000001, + F5B588D20156D78201000001, + F5B588EE0156DAF301000001, + F5B5890B0156DC2201000001, + ); + }; + F5B588100156D2A601000001 = { + children = ( + F5B5887C0156D6D901000001, + F5B5881A0156D40B01000001, + F5B5887F0156D6D901000001, + F5B588D80156D9D401000001, + F5B588F40156DC2201000001, + F5B588FC0156DC2201000001, + F5B588490156D40B01000001, + F5B588110156D30301000001, + ); + isa = PBXGroup; + refType = 4; + }; + F5B588110156D30301000001 = { + children = ( + F5B588120156D30301000001, + F5B588740156D5CB01000001, + F5B588D10156D78201000001, + F5B588ED0156DAF301000001, + F5B589030156DC2201000001, + ); + isa = PBXGroup; + name = Products; + refType = 4; + }; + F5B588120156D30301000001 = { + isa = PBXApplicationReference; + path = "ResKnife Cocoa.app"; + refType = 3; + }; + F5B588130156D30301000001 = { + buildPhases = ( + F5B588140156D30301000001, + F5B588150156D30301000001, + F5B588160156D30301000001, + F5B588170156D30301000001, + F5B588180156D30301000001, + ); + buildSettings = { + OPTIMIZATION_CFLAGS = ""; + OTHER_CFLAGS = ""; + OTHER_LDFLAGS = ""; + OTHER_REZFLAGS = ""; + PRODUCT_NAME = "ResKnife Cocoa"; + SECTORDER_FLAGS = ""; + WARNING_CFLAGS = "-Wmost -Wno-four-char-constants -Wno-unknown-pragmas"; + WRAPPER_EXTENSION = app; + }; + dependencies = ( + ); + isa = PBXApplicationTarget; + name = "ResKnife Cocoa"; + productInstallPath = "$(USER_APPS_DIR)"; + productName = "ResKnife Cocoa"; + productReference = F5B588120156D30301000001; + productSettingsXML = " + + + + CFBundleDevelopmentRegion + English + CFBundleDocumentTypes + + + CFBundleTypeExtensions + + rsrc + + CFBundleTypeIconFile + Resource file.icns + CFBundleTypeName + Resource file + CFBundleTypeOSTypes + + rsrc + RSRC + + CFBundleTypeRole + Editor + NSDocumentClass + ResourceDocument + + + CFBundleTypeExtensions + + icns + + CFBundleTypeIconFile + Icon file.icns + CFBundleTypeName + Icon file + CFBundleTypeOSTypes + + icns + + CFBundleTypeRole + Editor + + + CFBundleExecutable + ResKnife Cocoa + CFBundleGetInfoString + A resource editor for Mac OS X + CFBundleIconFile + ResKnife.icns + CFBundleIdentifier + com.nickshanks.resknife + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ResKnife + CFBundlePackageType + APPL + CFBundleShortVersionString + Development version 0.4d3 + CFBundleSignature + ResK + CFBundleVersion + 0.4d3 + NSMainNibFile + Application + NSPrincipalClass + NSApplication + + +"; + shouldUseHeadermap = 0; + }; + F5B588140156D30301000001 = { + buildActionMask = 2147483647; + files = ( + F5B5884E0156D40B01000001, + F5B5884F0156D40B01000001, + F5B588500156D40B01000001, + F5B588510156D40B01000001, + F5B588520156D40B01000001, + F5B588530156D40B01000001, + F5B588540156D40B01000001, + F5B588550156D40B01000001, + F5B588560156D40B01000001, + F5B588570156D40B01000001, + F5B588580156D40B01000001, + F5B588590156D40B01000001, + ); + isa = PBXHeadersBuildPhase; + name = Headers; + }; + F5B588150156D30301000001 = { + buildActionMask = 2147483647; + files = ( + F5B5885A0156D40B01000001, + F5B5885B0156D40B01000001, + F5B5885C0156D40B01000001, + F5B5885D0156D40B01000001, + F5B5885E0156D40B01000001, + F5B5885F0156D40B01000001, + F5B588600156D40B01000001, + F5B588610156D40B01000001, + F5B588620156D40B01000001, + F5B588630156D40B01000001, + F5B588640156D40B01000001, + F5730B940159528A01000001, + ); + isa = PBXResourcesBuildPhase; + name = "Bundle Resources"; + }; + F5B588160156D30301000001 = { + buildActionMask = 2147483647; + files = ( + F5B588650156D40B01000001, + F5B588660156D40B01000001, + F5B588670156D40B01000001, + F5B588680156D40B01000001, + F5B588690156D40B01000001, + F5B5886A0156D40B01000001, + F5B5886B0156D40B01000001, + F5B5886C0156D40B01000001, + F5B5886D0156D40B01000001, + F5B5886E0156D40B01000001, + F5B5886F0156D40B01000001, + F5B588700156D40B01000001, + F5B588710156D40B01000001, + ); + isa = PBXSourcesBuildPhase; + name = Sources; + }; + F5B588170156D30301000001 = { + buildActionMask = 2147483647; + files = ( + F5B588720156D40B01000001, + F5B588730156D40B01000001, + ); + isa = PBXFrameworksBuildPhase; + name = "Frameworks & Libraries"; + }; + F5B588180156D30301000001 = { + buildActionMask = 2147483647; + files = ( + ); + isa = PBXRezBuildPhase; + name = "ResourceManager Resources"; + }; + F5B5881A0156D40B01000001 = { + children = ( + F5B5881B0156D40B01000001, + F5B5881C0156D40B01000001, + F5B588350156D40B01000001, + ); + isa = PBXGroup; + path = Cocoa; + refType = 4; + }; + F5B5881B0156D40B01000001 = { + isa = PBXFileReference; + path = main.c; + refType = 4; + }; + F5B5881C0156D40B01000001 = { + children = ( + F5B5881D0156D40B01000001, + F5B5881E0156D40B01000001, + F5B5881F0156D40B01000001, + F5B588200156D40B01000001, + F5B588210156D40B01000001, + F5B588220156D40B01000001, + F5B588230156D40B01000001, + F5B588240156D40B01000001, + F5B588250156D40B01000001, + F5B588260156D40B01000001, + F5B588270156D40B01000001, + F5B588280156D40B01000001, + F5B588290156D40B01000001, + F5B5882A0156D40B01000001, + F5B5882B0156D40B01000001, + F5B5882C0156D40B01000001, + F5B5882D0156D40B01000001, + F5B5882E0156D40B01000001, + F5B5882F0156D40B01000001, + F5B588300156D40B01000001, + F5B588310156D40B01000001, + F5B588320156D40B01000001, + F5B588330156D40B01000001, + F5B588340156D40B01000001, + ); + isa = PBXGroup; + path = Classes; + refType = 4; + }; + F5B5881D0156D40B01000001 = { + isa = PBXFileReference; + path = ApplicationDelegate.h; + refType = 4; + }; + F5B5881E0156D40B01000001 = { + isa = PBXFileReference; + path = ApplicationDelegate.m; + refType = 4; + }; + F5B5881F0156D40B01000001 = { + isa = PBXFileReference; + path = AttributesFormatter.h; + refType = 4; + }; + F5B588200156D40B01000001 = { + isa = PBXFileReference; + path = AttributesFormatter.m; + refType = 4; + }; + F5B588210156D40B01000001 = { + isa = PBXFileReference; + path = CreateResourceSheetController.h; + refType = 4; + }; + F5B588220156D40B01000001 = { + isa = PBXFileReference; + path = CreateResourceSheetController.m; + refType = 4; + }; + F5B588230156D40B01000001 = { + isa = PBXFileReference; + path = InfoWindow.h; + refType = 4; + }; + F5B588240156D40B01000001 = { + isa = PBXFileReference; + path = InfoWindow.m; + refType = 4; + }; + F5B588250156D40B01000001 = { + isa = PBXFileReference; + path = InfoWindowController.h; + refType = 4; + }; + F5B588260156D40B01000001 = { + isa = PBXFileReference; + path = InfoWindowController.m; + refType = 4; + }; + F5B588270156D40B01000001 = { + isa = PBXFileReference; + path = NameFormatter.h; + refType = 4; + }; + F5B588280156D40B01000001 = { + isa = PBXFileReference; + path = NameFormatter.m; + refType = 4; + }; + F5B588290156D40B01000001 = { + isa = PBXFileReference; + path = OutlineViewDelegate.h; + refType = 4; + }; + F5B5882A0156D40B01000001 = { + isa = PBXFileReference; + path = OutlineViewDelegate.m; + refType = 4; + }; + F5B5882B0156D40B01000001 = { + isa = PBXFileReference; + path = PrefsWindowController.h; + refType = 4; + }; + F5B5882C0156D40B01000001 = { + isa = PBXFileReference; + path = PrefsWindowController.m; + refType = 4; + }; + F5B5882D0156D40B01000001 = { + isa = PBXFileReference; + path = Resource.h; + refType = 4; + }; + F5B5882E0156D40B01000001 = { + isa = PBXFileReference; + path = Resource.m; + refType = 4; + }; + F5B5882F0156D40B01000001 = { + isa = PBXFileReference; + path = ResourceDataSource.h; + refType = 4; + }; + F5B588300156D40B01000001 = { + isa = PBXFileReference; + path = ResourceDataSource.m; + refType = 4; + }; + F5B588310156D40B01000001 = { + isa = PBXFileReference; + path = ResourceDocument.h; + refType = 4; + }; + F5B588320156D40B01000001 = { + isa = PBXFileReference; + path = ResourceDocument.m; + refType = 4; + }; + F5B588330156D40B01000001 = { + isa = PBXFileReference; + path = SizeFormatter.h; + refType = 4; + }; + F5B588340156D40B01000001 = { + isa = PBXFileReference; + path = SizeFormatter.m; + refType = 4; + }; + F5B588350156D40B01000001 = { + children = ( + F5B588360156D40B01000001, + F5B588380156D40B01000001, + F5B5883A0156D40B01000001, + F5B5883C0156D40B01000001, + F5B5883E0156D40B01000001, + F5B588400156D40B01000001, + F5B588420156D40B01000001, + F5B588440156D40B01000001, + F5730B930159528A01000001, + F5B588460156D40B01000001, + F5B588470156D40B01000001, + F5B588480156D40B01000001, + ); + isa = PBXGroup; + path = Resources; + refType = 4; + }; + F5B588360156D40B01000001 = { + children = ( + F5B588370156D40B01000001, + ); + isa = PBXVariantGroup; + name = AboutPanel.nib; + path = Cocoa; + refType = 2; + }; + F5B588370156D40B01000001 = { + isa = PBXFileReference; + name = English; + path = English.lproj/AboutPanel.nib; + refType = 4; + }; + F5B588380156D40B01000001 = { + children = ( + F5B588390156D40B01000001, + ); + isa = PBXVariantGroup; + name = Application.nib; + path = Cocoa; + refType = 2; + }; + F5B588390156D40B01000001 = { + isa = PBXFileReference; + name = English; + path = English.lproj/Application.nib; + refType = 4; + }; + F5B5883A0156D40B01000001 = { + children = ( + F5B5883B0156D40B01000001, + ); + isa = PBXVariantGroup; + name = InfoWindow.nib; + path = Cocoa; + refType = 2; + }; + F5B5883B0156D40B01000001 = { + isa = PBXFileReference; + name = English; + path = English.lproj/InfoWindow.nib; + refType = 4; + }; + F5B5883C0156D40B01000001 = { + children = ( + F5B5883D0156D40B01000001, + ); + isa = PBXVariantGroup; + name = Inspector.nib; + path = Cocoa; + refType = 2; + }; + F5B5883D0156D40B01000001 = { + isa = PBXFileReference; + name = English; + path = English.lproj/Inspector.nib; + refType = 4; + }; + F5B5883E0156D40B01000001 = { + children = ( + F5B5883F0156D40B01000001, + ); + isa = PBXVariantGroup; + name = PrefsWindow.nib; + path = Cocoa; + refType = 2; + }; + F5B5883F0156D40B01000001 = { + isa = PBXFileReference; + name = English; + path = English.lproj/PrefsWindow.nib; + refType = 4; + }; + F5B588400156D40B01000001 = { + children = ( + F5B588410156D40B01000001, + ); + isa = PBXVariantGroup; + name = ResourceDocument.nib; + path = Cocoa; + refType = 2; + }; + F5B588410156D40B01000001 = { + isa = PBXFileReference; + name = English; + path = English.lproj/ResourceDocument.nib; + refType = 4; + }; + F5B588420156D40B01000001 = { + children = ( + F5B588430156D40B01000001, + ); + isa = PBXVariantGroup; + name = InfoPlist.strings; + path = Cocoa; + refType = 2; + }; + F5B588430156D40B01000001 = { + isa = PBXFileReference; + name = English; + path = English.lproj/InfoPlist.strings; + refType = 4; + }; + F5B588440156D40B01000001 = { + children = ( + F5B588450156D40B01000001, + ); + isa = PBXVariantGroup; + name = Localizable.strings; + path = Cocoa; + refType = 2; + }; + F5B588450156D40B01000001 = { + isa = PBXFileReference; + name = English; + path = English.lproj/Localizable.strings; + refType = 4; + }; + F5B588460156D40B01000001 = { + isa = PBXFileReference; + path = ResKnife.icns; + refType = 4; + }; + F5B588470156D40B01000001 = { + isa = PBXFileReference; + path = "Resource file.icns"; + refType = 4; + }; + F5B588480156D40B01000001 = { + isa = PBXFileReference; + path = "Icon file.icns"; + refType = 4; + }; + F5B588490156D40B01000001 = { + children = ( + F5B5884A0156D40B01000001, + F5B5884B0156D40B01000001, + F5B5884C0156D40B01000001, + F5B5884D0156D40B01000001, + ); + isa = PBXGroup; + name = Frameworks; + refType = 4; + }; + F5B5884A0156D40B01000001 = { + isa = PBXFrameworkReference; + name = Carbon.framework; + path = /System/Library/Frameworks/Carbon.framework; + refType = 0; + }; + F5B5884B0156D40B01000001 = { + isa = PBXFrameworkReference; + name = Cocoa.framework; + path = /System/Library/Frameworks/Cocoa.framework; + refType = 0; + }; + F5B5884C0156D40B01000001 = { + isa = PBXFrameworkReference; + name = AppKit.framework; + path = /System/Library/Frameworks/AppKit.framework; + refType = 0; + }; + F5B5884D0156D40B01000001 = { + isa = PBXFrameworkReference; + name = Foundation.framework; + path = /System/Library/Frameworks/Foundation.framework; + refType = 0; + }; + F5B5884E0156D40B01000001 = { + fileRef = F5B5881D0156D40B01000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B5884F0156D40B01000001 = { + fileRef = F5B5881F0156D40B01000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B588500156D40B01000001 = { + fileRef = F5B588210156D40B01000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B588510156D40B01000001 = { + fileRef = F5B588230156D40B01000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B588520156D40B01000001 = { + fileRef = F5B588250156D40B01000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B588530156D40B01000001 = { + fileRef = F5B588270156D40B01000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B588540156D40B01000001 = { + fileRef = F5B588290156D40B01000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B588550156D40B01000001 = { + fileRef = F5B5882B0156D40B01000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B588560156D40B01000001 = { + fileRef = F5B5882D0156D40B01000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B588570156D40B01000001 = { + fileRef = F5B5882F0156D40B01000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B588580156D40B01000001 = { + fileRef = F5B588310156D40B01000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B588590156D40B01000001 = { + fileRef = F5B588330156D40B01000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B5885A0156D40B01000001 = { + fileRef = F5B588360156D40B01000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B5885B0156D40B01000001 = { + fileRef = F5B588380156D40B01000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B5885C0156D40B01000001 = { + fileRef = F5B5883A0156D40B01000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B5885D0156D40B01000001 = { + fileRef = F5B5883C0156D40B01000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B5885E0156D40B01000001 = { + fileRef = F5B5883E0156D40B01000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B5885F0156D40B01000001 = { + fileRef = F5B588400156D40B01000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B588600156D40B01000001 = { + fileRef = F5B588420156D40B01000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B588610156D40B01000001 = { + fileRef = F5B588440156D40B01000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B588620156D40B01000001 = { + fileRef = F5B588460156D40B01000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B588630156D40B01000001 = { + fileRef = F5B588470156D40B01000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B588640156D40B01000001 = { + fileRef = F5B588480156D40B01000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B588650156D40B01000001 = { + fileRef = F5B5881B0156D40B01000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B588660156D40B01000001 = { + fileRef = F5B5881E0156D40B01000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B588670156D40B01000001 = { + fileRef = F5B588200156D40B01000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B588680156D40B01000001 = { + fileRef = F5B588220156D40B01000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B588690156D40B01000001 = { + fileRef = F5B588240156D40B01000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B5886A0156D40B01000001 = { + fileRef = F5B588260156D40B01000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B5886B0156D40B01000001 = { + fileRef = F5B588280156D40B01000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B5886C0156D40B01000001 = { + fileRef = F5B5882A0156D40B01000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B5886D0156D40B01000001 = { + fileRef = F5B5882C0156D40B01000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B5886E0156D40B01000001 = { + fileRef = F5B5882E0156D40B01000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B5886F0156D40B01000001 = { + fileRef = F5B588300156D40B01000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B588700156D40B01000001 = { + fileRef = F5B588320156D40B01000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B588710156D40B01000001 = { + fileRef = F5B588340156D40B01000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B588720156D40B01000001 = { + fileRef = F5B5884B0156D40B01000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B588730156D40B01000001 = { + fileRef = F5B5884A0156D40B01000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B588740156D5CB01000001 = { + isa = PBXApplicationReference; + path = "ResKnife Carbon.app"; + refType = 3; + }; + F5B588750156D5CB01000001 = { + buildPhases = ( + F5B588760156D5CB01000001, + F5B588770156D5CB01000001, + F5B588780156D5CB01000001, + F5B588790156D5CB01000001, + F5B5887A0156D5CB01000001, + ); + buildSettings = { + LIBRARY_SEARCH_PATHS = ""; + OPTIMIZATION_CFLAGS = ""; + OTHER_CFLAGS = ""; + OTHER_LDFLAGS = ""; + OTHER_REZFLAGS = ""; + PRODUCT_NAME = "ResKnife Carbon"; + SECTORDER_FLAGS = ""; + WARNING_CFLAGS = "-Wmost -Wno-four-char-constants -Wno-unknown-pragmas"; + WRAPPER_EXTENSION = app; + }; + dependencies = ( + ); + isa = PBXApplicationTarget; + name = "ResKnife Carbon"; + productInstallPath = "$(USER_APPS_DIR)"; + productName = "ResKnife Carbon"; + productReference = F5B588740156D5CB01000001; + productSettingsXML = " + + + + CFBundleDevelopmentRegion + English + CFBundleDocumentTypes + + + CFBundleTypeExtensions + + rsrc + + CFBundleTypeIconFile + Resource file.icns + CFBundleTypeName + Resource file + CFBundleTypeOSTypes + + rsrc + RSRC + + CFBundleTypeRole + Editor + + + CFBundleTypeExtensions + + icns + + CFBundleTypeIconFile + Icon file.icns + CFBundleTypeName + Icon file + CFBundleTypeOSTypes + + icns + + CFBundleTypeRole + Editor + + + CFBundleExecutable + ResKnife Carbon + CFBundleGetInfoString + A resource editor for Mac OS X + CFBundleIconFile + ResKnife.icns + CFBundleIdentifier + com.nickshanks.resknife + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ResKnife + CFBundlePackageType + APPL + CFBundleShortVersionString + Development version 0.4d3 + CFBundleSignature + ResK + CFBundleVersion + 0.4d3 + + +"; + shouldUseHeadermap = 0; + }; + F5B588760156D5CB01000001 = { + buildActionMask = 2147483647; + files = ( + F5B588A80156D6D901000001, + F5B588A90156D6D901000001, + F5B588AA0156D6D901000001, + F5B588AB0156D6D901000001, + F5B588AC0156D6D901000001, + F5B588AD0156D6D901000001, + F5B588AE0156D6D901000001, + F5B588AF0156D6D901000001, + F5B588B00156D6D901000001, + F5B588B10156D6D901000001, + F5B588B20156D6D901000001, + F5B588B30156D6D901000001, + F5B588B40156D6D901000001, + F5B588B50156D6D901000001, + F5B588B60156D6D901000001, + F5B588B70156D6D901000001, + F5B588B80156D6D901000001, + F5B588B90156D6D901000001, + F5B588BA0156D6D901000001, + ); + isa = PBXHeadersBuildPhase; + name = Headers; + }; + F5B588770156D5CB01000001 = { + buildActionMask = 2147483647; + files = ( + F5B588BC0156D6D901000001, + ); + isa = PBXResourcesBuildPhase; + name = "Bundle Resources"; + }; + F5B588780156D5CB01000001 = { + buildActionMask = 2147483647; + files = ( + F5B588BD0156D6D901000001, + F5B588BE0156D6D901000001, + F5B588BF0156D6D901000001, + F5B588C00156D6D901000001, + F5B588C10156D6D901000001, + F5B588C20156D6D901000001, + F5B588C30156D6D901000001, + F5B588C40156D6D901000001, + F5B588C50156D6D901000001, + F5B588C60156D6D901000001, + F5B588C70156D6D901000001, + F5B588C80156D6D901000001, + F5B588C90156D6D901000001, + F5B588CA0156D6D901000001, + F5B588CB0156D6D901000001, + ); + isa = PBXSourcesBuildPhase; + name = Sources; + }; + F5B588790156D5CB01000001 = { + buildActionMask = 2147483647; + files = ( + F5B588CC0156D6D901000001, + ); + isa = PBXFrameworksBuildPhase; + name = "Frameworks & Libraries"; + }; + F5B5887A0156D5CB01000001 = { + buildActionMask = 2147483647; + files = ( + F5B588CE0156D6D901000001, + F5B588CF0156D6D901000001, + F5B588D00156D6D901000001, + ); + isa = PBXRezBuildPhase; + name = "ResourceManager Resources"; + }; + F5B5887C0156D6D901000001 = { + children = ( + F5B5887D0156D6D901000001, + F5B5887E0156D6D901000001, + ); + isa = PBXGroup; + path = "Prefix Files"; + refType = 4; + }; + F5B5887D0156D6D901000001 = { + isa = PBXFileReference; + path = "Carbon Prefix.h"; + refType = 4; + }; + F5B5887E0156D6D901000001 = { + isa = PBXFileReference; + path = "Classic Prefix.h"; + refType = 4; + }; + F5B5887F0156D6D901000001 = { + children = ( + F5B588810156D6D901000001, + F5B588820156D6D901000001, + F5B588800156D6D901000001, + F5B588830156D6D901000001, + F5B588A20156D6D901000001, + F5B588A70156D6D901000001, + ); + isa = PBXGroup; + path = Carbon; + refType = 4; + }; + F5B588800156D6D901000001 = { + isa = PBXFileReference; + path = Transfer.h; + refType = 4; + }; + F5B588810156D6D901000001 = { + isa = PBXFileReference; + path = ResKnife.h; + refType = 4; + }; + F5B588820156D6D901000001 = { + isa = PBXFileReference; + path = Generic.h; + refType = 4; + }; + F5B588830156D6D901000001 = { + children = ( + F5B588840156D6D901000001, + F5B588850156D6D901000001, + F5B588860156D6D901000001, + F5B588870156D6D901000001, + F5B588880156D6D901000001, + F5B588890156D6D901000001, + F5B5888A0156D6D901000001, + F5B5888B0156D6D901000001, + F5B5888C0156D6D901000001, + F5B5888D0156D6D901000001, + F5B5888E0156D6D901000001, + F5B5888F0156D6D901000001, + F5B588900156D6D901000001, + F5B588910156D6D901000001, + F5B588920156D6D901000001, + F5B588930156D6D901000001, + F5B588940156D6D901000001, + F5B588950156D6D901000001, + F5B588960156D6D901000001, + F5B588970156D6D901000001, + F5B588980156D6D901000001, + F5B588990156D6D901000001, + F5B5889A0156D6D901000001, + F5B5889B0156D6D901000001, + F5B5889C0156D6D901000001, + F5B5889D0156D6D901000001, + F5B5889E0156D6D901000001, + F5B5889F0156D6D901000001, + F5B588A00156D6D901000001, + F5B588A10156D6D901000001, + ); + isa = PBXGroup; + path = Classes; + refType = 4; + }; + F5B588840156D6D901000001 = { + isa = PBXFileReference; + path = Application.h; + refType = 4; + }; + F5B588850156D6D901000001 = { + isa = PBXFileReference; + path = Application.cpp; + refType = 4; + }; + F5B588860156D6D901000001 = { + isa = PBXFileReference; + path = Asynchronous.h; + refType = 4; + }; + F5B588870156D6D901000001 = { + isa = PBXFileReference; + path = Asynchronous.cpp; + refType = 4; + }; + F5B588880156D6D901000001 = { + isa = PBXFileReference; + path = DataBrowser.h; + refType = 4; + }; + F5B588890156D6D901000001 = { + isa = PBXFileReference; + path = DataBrowser.cpp; + refType = 4; + }; + F5B5888A0156D6D901000001 = { + isa = PBXFileReference; + path = EditorWindow.h; + refType = 4; + }; + F5B5888B0156D6D901000001 = { + isa = PBXFileReference; + path = EditorWindow.cpp; + refType = 4; + }; + F5B5888C0156D6D901000001 = { + isa = PBXFileReference; + path = Errors.h; + refType = 4; + }; + F5B5888D0156D6D901000001 = { + isa = PBXFileReference; + path = Errors.cpp; + refType = 4; + }; + F5B5888E0156D6D901000001 = { + isa = PBXFileReference; + path = Files.h; + refType = 4; + }; + F5B5888F0156D6D901000001 = { + isa = PBXFileReference; + path = Files.cpp; + refType = 4; + }; + F5B588900156D6D901000001 = { + isa = PBXFileReference; + path = FileWindow.h; + refType = 4; + }; + F5B588910156D6D901000001 = { + isa = PBXFileReference; + path = FileWindow.cpp; + refType = 4; + }; + F5B588920156D6D901000001 = { + isa = PBXFileReference; + path = HostCallbacks.h; + refType = 4; + }; + F5B588930156D6D901000001 = { + isa = PBXFileReference; + path = HostCallbacks.cpp; + refType = 4; + }; + F5B588940156D6D901000001 = { + isa = PBXFileReference; + path = InspectorWindow.h; + refType = 4; + }; + F5B588950156D6D901000001 = { + isa = PBXFileReference; + path = InspectorWindow.cpp; + refType = 4; + }; + F5B588960156D6D901000001 = { + isa = PBXFileReference; + path = PickerWindow.h; + refType = 4; + }; + F5B588970156D6D901000001 = { + isa = PBXFileReference; + path = PickerWindow.cpp; + refType = 4; + }; + F5B588980156D6D901000001 = { + isa = PBXFileReference; + path = PlugObject.h; + refType = 4; + }; + F5B588990156D6D901000001 = { + isa = PBXFileReference; + path = PlugObject.cpp; + refType = 4; + }; + F5B5889A0156D6D901000001 = { + isa = PBXFileReference; + path = PlugWindow.h; + refType = 4; + }; + F5B5889B0156D6D901000001 = { + isa = PBXFileReference; + path = PlugWindow.cpp; + refType = 4; + }; + F5B5889C0156D6D901000001 = { + isa = PBXFileReference; + path = ResourceObject.h; + refType = 4; + }; + F5B5889D0156D6D901000001 = { + isa = PBXFileReference; + path = ResourceObject.cpp; + refType = 4; + }; + F5B5889E0156D6D901000001 = { + isa = PBXFileReference; + path = Utility.h; + refType = 4; + }; + F5B5889F0156D6D901000001 = { + isa = PBXFileReference; + path = Utility.cpp; + refType = 4; + }; + F5B588A00156D6D901000001 = { + isa = PBXFileReference; + path = WindowObject.h; + refType = 4; + }; + F5B588A10156D6D901000001 = { + isa = PBXFileReference; + path = WindowObject.cpp; + refType = 4; + }; + F5B588A20156D6D901000001 = { + children = ( + F5B588A30156D6D901000001, + F5B588A40156D6D901000001, + F5B588A50156D6D901000001, + F5B588A60156D6D901000001, + ); + isa = PBXGroup; + path = Resources; + refType = 4; + }; + F5B588A30156D6D901000001 = { + isa = PBXFileReference; + path = Carbon.r; + refType = 4; + }; + F5B588A40156D6D901000001 = { + isa = PBXFileReference; + path = ResKnife.r; + refType = 4; + }; + F5B588A50156D6D901000001 = { + isa = PBXFileReference; + path = ResKnife.rsrc; + refType = 4; + }; + F5B588A60156D6D901000001 = { + isa = PBXFileReference; + path = ResKnife.nib; + refType = 4; + }; + F5B588A70156D6D901000001 = { + isa = PBXFileReference; + name = ResKnife.lib; + path = Carbon/ResKnife.lib; + refType = 2; + }; + F5B588A80156D6D901000001 = { + fileRef = F5B588840156D6D901000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B588A90156D6D901000001 = { + fileRef = F5B588860156D6D901000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B588AA0156D6D901000001 = { + fileRef = F5B588880156D6D901000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B588AB0156D6D901000001 = { + fileRef = F5B5888A0156D6D901000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B588AC0156D6D901000001 = { + fileRef = F5B5888C0156D6D901000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B588AD0156D6D901000001 = { + fileRef = F5B5888E0156D6D901000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B588AE0156D6D901000001 = { + fileRef = F5B588900156D6D901000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B588AF0156D6D901000001 = { + fileRef = F5B588920156D6D901000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B588B00156D6D901000001 = { + fileRef = F5B588940156D6D901000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B588B10156D6D901000001 = { + fileRef = F5B588960156D6D901000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B588B20156D6D901000001 = { + fileRef = F5B588980156D6D901000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B588B30156D6D901000001 = { + fileRef = F5B5889A0156D6D901000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B588B40156D6D901000001 = { + fileRef = F5B5889C0156D6D901000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B588B50156D6D901000001 = { + fileRef = F5B5889E0156D6D901000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B588B60156D6D901000001 = { + fileRef = F5B588A00156D6D901000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B588B70156D6D901000001 = { + fileRef = F5B588820156D6D901000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B588B80156D6D901000001 = { + fileRef = F5B588810156D6D901000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B588B90156D6D901000001 = { + fileRef = F5B588800156D6D901000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B588BA0156D6D901000001 = { + fileRef = F5B5887D0156D6D901000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B588BC0156D6D901000001 = { + fileRef = F5B588A60156D6D901000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B588BD0156D6D901000001 = { + fileRef = F5B588850156D6D901000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B588BE0156D6D901000001 = { + fileRef = F5B588870156D6D901000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B588BF0156D6D901000001 = { + fileRef = F5B588890156D6D901000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B588C00156D6D901000001 = { + fileRef = F5B5888B0156D6D901000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B588C10156D6D901000001 = { + fileRef = F5B5888D0156D6D901000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B588C20156D6D901000001 = { + fileRef = F5B5888F0156D6D901000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B588C30156D6D901000001 = { + fileRef = F5B588910156D6D901000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B588C40156D6D901000001 = { + fileRef = F5B588930156D6D901000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B588C50156D6D901000001 = { + fileRef = F5B588950156D6D901000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B588C60156D6D901000001 = { + fileRef = F5B588970156D6D901000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B588C70156D6D901000001 = { + fileRef = F5B588990156D6D901000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B588C80156D6D901000001 = { + fileRef = F5B5889B0156D6D901000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B588C90156D6D901000001 = { + fileRef = F5B5889D0156D6D901000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B588CA0156D6D901000001 = { + fileRef = F5B5889F0156D6D901000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B588CB0156D6D901000001 = { + fileRef = F5B588A10156D6D901000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B588CC0156D6D901000001 = { + fileRef = F5B588A70156D6D901000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B588CE0156D6D901000001 = { + fileRef = F5B588A30156D6D901000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B588CF0156D6D901000001 = { + fileRef = F5B588A40156D6D901000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B588D00156D6D901000001 = { + fileRef = F5B588A50156D6D901000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B588D10156D78201000001 = { + isa = PBXBundleReference; + path = "Hex Editor.bundle"; + refType = 3; + }; + F5B588D20156D78201000001 = { + buildPhases = ( + F5B588D30156D78201000001, + F5B588D40156D78201000001, + F5B588D50156D78201000001, + F5B588D60156D78201000001, + F5B588D70156D78201000001, + ); + buildSettings = { + OPTIMIZATION_CFLAGS = ""; + OTHER_CFLAGS = ""; + OTHER_LDFLAGS = "-bundle -undefined suppress"; + OTHER_REZFLAGS = ""; + PRODUCT_NAME = "Hex Editor"; + SECTORDER_FLAGS = ""; + WARNING_CFLAGS = "-Wmost -Wno-four-char-constants -Wno-unknown-pragmas"; + WRAPPER_EXTENSION = bundle; + }; + dependencies = ( + ); + isa = PBXBundleTarget; + name = "Hex Editor"; + productInstallPath = "$(USER_LIBRARY_DIR)/Bundles"; + productName = "Hex Editor"; + productReference = F5B588D10156D78201000001; + productSettingsXML = " + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + Hex Editor + CFBundleGetInfoString + The default hexadecimal editor for ResKnife + CFBundleIconFile + + CFBundleIdentifier + com.nickshanks.resknife.hexadecimal + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + Hex Editor + CFBundlePackageType + BNDL + CFBundleShortVersionString + ResKnife Hex Editor 0.1d1 + CFBundleSignature + ResK + CFBundleVersion + 0.1d1 + + +"; + shouldUseHeadermap = 0; + }; + F5B588D30156D78201000001 = { + buildActionMask = 2147483647; + files = ( + F5B588E70156D9D401000001, + F5B588E30156D9D401000001, + F5B588E40156D9D401000001, + F5B588E50156D9D401000001, + F5B588E60156D9D401000001, + ); + isa = PBXHeadersBuildPhase; + name = Headers; + }; + F5B588D40156D78201000001 = { + buildActionMask = 2147483647; + files = ( + ); + isa = PBXResourcesBuildPhase; + name = "Bundle Resources"; + }; + F5B588D50156D78201000001 = { + buildActionMask = 2147483647; + files = ( + F5B588E80156D9D401000001, + F5B588E90156D9D401000001, + F5B588EA0156D9D401000001, + F5B588EB0156D9D401000001, + ); + isa = PBXSourcesBuildPhase; + name = Sources; + }; + F5B588D60156D78201000001 = { + buildActionMask = 2147483647; + files = ( + F5B588EC0156DA1601000001, + ); + isa = PBXFrameworksBuildPhase; + name = "Frameworks & Libraries"; + }; + F5B588D70156D78201000001 = { + buildActionMask = 2147483647; + files = ( + ); + isa = PBXRezBuildPhase; + name = "ResourceManager Resources"; + }; + F5B588D80156D9D401000001 = { + children = ( + F5B588D90156D9D401000001, + F5B588DA0156D9D401000001, + ); + isa = PBXGroup; + path = "Hex Editor"; + refType = 4; + }; + F5B588D90156D9D401000001 = { + isa = PBXFileReference; + path = "Hex Editor.h"; + refType = 4; + }; + F5B588DA0156D9D401000001 = { + children = ( + F5B588DB0156D9D401000001, + F5B588DC0156D9D401000001, + F5B588DD0156D9D401000001, + F5B588DE0156D9D401000001, + F5B588DF0156D9D401000001, + F5B588E00156D9D401000001, + F5B588E10156D9D401000001, + F5B588E20156D9D401000001, + ); + isa = PBXGroup; + path = Classes; + refType = 4; + }; + F5B588DB0156D9D401000001 = { + isa = PBXFileReference; + path = Events.h; + refType = 4; + }; + F5B588DC0156D9D401000001 = { + isa = PBXFileReference; + path = Events.cpp; + refType = 4; + }; + F5B588DD0156D9D401000001 = { + isa = PBXFileReference; + path = HexWindow.h; + refType = 4; + }; + F5B588DE0156D9D401000001 = { + isa = PBXFileReference; + path = HexWindow.cpp; + refType = 4; + }; + F5B588DF0156D9D401000001 = { + isa = PBXFileReference; + path = Initalisation.h; + refType = 4; + }; + F5B588E00156D9D401000001 = { + isa = PBXFileReference; + path = Initalisation.cpp; + refType = 4; + }; + F5B588E10156D9D401000001 = { + isa = PBXFileReference; + path = Utility.h; + refType = 4; + }; + F5B588E20156D9D401000001 = { + isa = PBXFileReference; + path = Utility.cpp; + refType = 4; + }; + F5B588E30156D9D401000001 = { + fileRef = F5B588DB0156D9D401000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B588E40156D9D401000001 = { + fileRef = F5B588DD0156D9D401000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B588E50156D9D401000001 = { + fileRef = F5B588DF0156D9D401000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B588E60156D9D401000001 = { + fileRef = F5B588E10156D9D401000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B588E70156D9D401000001 = { + fileRef = F5B588D90156D9D401000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B588E80156D9D401000001 = { + fileRef = F5B588DC0156D9D401000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B588E90156D9D401000001 = { + fileRef = F5B588DE0156D9D401000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B588EA0156D9D401000001 = { + fileRef = F5B588E00156D9D401000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B588EB0156D9D401000001 = { + fileRef = F5B588E20156D9D401000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B588EC0156DA1601000001 = { + fileRef = F5B5884A0156D40B01000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B588ED0156DAF301000001 = { + isa = PBXBundleReference; + path = "Template Editor.bundle"; + refType = 3; + }; + F5B588EE0156DAF301000001 = { + buildPhases = ( + F5B588EF0156DAF301000001, + F5B588F00156DAF301000001, + F5B588F10156DAF301000001, + F5B588F20156DAF301000001, + F5B588F30156DAF301000001, + ); + buildSettings = { + OPTIMIZATION_CFLAGS = ""; + OTHER_CFLAGS = ""; + OTHER_LDFLAGS = "-bundle -undefined suppress"; + OTHER_REZFLAGS = ""; + PRODUCT_NAME = "Template Editor"; + SECTORDER_FLAGS = ""; + WARNING_CFLAGS = "-Wmost -Wno-four-char-constants -Wno-unknown-pragmas"; + WRAPPER_EXTENSION = bundle; + }; + dependencies = ( + ); + isa = PBXBundleTarget; + name = "Template Editor"; + productInstallPath = "$(USER_LIBRARY_DIR)/Bundles"; + productName = "Template Editor"; + productReference = F5B588ED0156DAF301000001; + productSettingsXML = " + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + Template Editor + CFBundleGetInfoString + The default template editor for ResKnife + CFBundleIconFile + + CFBundleIdentifier + com.nickshanks.resknife.template + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + Template Editor + CFBundlePackageType + BNDL + CFBundleShortVersionString + ResKnife Template Editor 0.1d1 + CFBundleSignature + ResK + CFBundleVersion + 0.1d1 + + +"; + shouldUseHeadermap = 0; + }; + F5B588EF0156DAF301000001 = { + buildActionMask = 2147483647; + files = ( + F5B589040156DC2201000001, + F5B589050156DC2201000001, + F5B589060156DC2201000001, + ); + isa = PBXHeadersBuildPhase; + name = Headers; + }; + F5B588F00156DAF301000001 = { + buildActionMask = 2147483647; + files = ( + ); + isa = PBXResourcesBuildPhase; + name = "Bundle Resources"; + }; + F5B588F10156DAF301000001 = { + buildActionMask = 2147483647; + files = ( + F5B589070156DC2201000001, + F5B589080156DC2201000001, + ); + isa = PBXSourcesBuildPhase; + name = Sources; + }; + F5B588F20156DAF301000001 = { + buildActionMask = 2147483647; + files = ( + F5B589090156DC2201000001, + ); + isa = PBXFrameworksBuildPhase; + name = "Frameworks & Libraries"; + }; + F5B588F30156DAF301000001 = { + buildActionMask = 2147483647; + files = ( + F5B5890A0156DC2201000001, + ); + isa = PBXRezBuildPhase; + name = "ResourceManager Resources"; + }; + F5B588F40156DC2201000001 = { + children = ( + F5B588F50156DC2201000001, + F5B588F60156DC2201000001, + F5B588FB0156DC2201000001, + ); + isa = PBXGroup; + path = "Template Editor"; + refType = 4; + }; + F5B588F50156DC2201000001 = { + isa = PBXFileReference; + path = "Template Editor.h"; + refType = 4; + }; + F5B588F60156DC2201000001 = { + children = ( + F5B588F70156DC2201000001, + F5B588F80156DC2201000001, + F5B588F90156DC2201000001, + F5B588FA0156DC2201000001, + ); + isa = PBXGroup; + path = Classes; + refType = 4; + }; + F5B588F70156DC2201000001 = { + isa = PBXFileReference; + path = Initalisation.h; + refType = 4; + }; + F5B588F80156DC2201000001 = { + isa = PBXFileReference; + path = Initalisation.cpp; + refType = 4; + }; + F5B588F90156DC2201000001 = { + isa = PBXFileReference; + path = TemplateWindow.h; + refType = 4; + }; + F5B588FA0156DC2201000001 = { + isa = PBXFileReference; + path = TemplateWindow.cpp; + refType = 4; + }; + F5B588FB0156DC2201000001 = { + isa = PBXFileReference; + path = "Template Editor.r"; + refType = 4; + }; + F5B588FC0156DC2201000001 = { + children = ( + F5B588FD0156DC2201000001, + F5B588FE0156DC2201000001, + ); + isa = PBXGroup; + path = "PICT Editor"; + refType = 4; + }; + F5B588FD0156DC2201000001 = { + isa = PBXFileReference; + path = "PICT Editor.h"; + refType = 4; + }; + F5B588FE0156DC2201000001 = { + children = ( + F5B588FF0156DC2201000001, + F5B589000156DC2201000001, + F5B589010156DC2201000001, + F5B589020156DC2201000001, + ); + isa = PBXGroup; + path = Classes; + refType = 4; + }; + F5B588FF0156DC2201000001 = { + isa = PBXFileReference; + path = Initalisation.h; + refType = 4; + }; + F5B589000156DC2201000001 = { + isa = PBXFileReference; + path = Initalisation.cpp; + refType = 4; + }; + F5B589010156DC2201000001 = { + isa = PBXFileReference; + path = Parser.h; + refType = 4; + }; + F5B589020156DC2201000001 = { + isa = PBXFileReference; + path = PictWindow.cpp; + refType = 4; + }; + F5B589030156DC2201000001 = { + isa = PBXBundleReference; + path = "PICT Editor.bundle"; + refType = 3; + }; + F5B589040156DC2201000001 = { + fileRef = F5B588F50156DC2201000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B589050156DC2201000001 = { + fileRef = F5B588F70156DC2201000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B589060156DC2201000001 = { + fileRef = F5B588F90156DC2201000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B589070156DC2201000001 = { + fileRef = F5B588F80156DC2201000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B589080156DC2201000001 = { + fileRef = F5B588FA0156DC2201000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B589090156DC2201000001 = { + fileRef = F5B5884A0156D40B01000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B5890A0156DC2201000001 = { + fileRef = F5B588FB0156DC2201000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B5890B0156DC2201000001 = { + buildPhases = ( + F5B5890C0156DC2201000001, + F5B589100156DC2201000001, + F5B589110156DC2201000001, + F5B589140156DC2201000001, + F5B589160156DC2201000001, + ); + buildSettings = { + OPTIMIZATION_CFLAGS = ""; + OTHER_CFLAGS = ""; + OTHER_LDFLAGS = "-bundle -undefined suppress"; + OTHER_REZFLAGS = ""; + PRODUCT_NAME = "PICT Editor"; + SECTORDER_FLAGS = ""; + WARNING_CFLAGS = "-Wmost -Wno-four-char-constants -Wno-unknown-pragmas"; + WRAPPER_EXTENSION = bundle; + }; + dependencies = ( + ); + isa = PBXBundleTarget; + name = "PICT Editor"; + productInstallPath = "$(USER_LIBRARY_DIR)/Bundles"; + productName = "PICT Editor"; + productReference = F5B589030156DC2201000001; + productSettingsXML = " + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + PICT Editor + CFBundleGetInfoString + The defailt PICT editor supplied with ResKnife + CFBundleIconFile + + CFBundleIdentifier + com.nickshanks.resknife.PICT + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + PICT Editor + CFBundlePackageType + BNDL + CFBundleShortVersionString + ResKnife PICT Editor 0.1d1 + CFBundleSignature + ResK + CFBundleVersion + 0.1d1 + + +"; + shouldUseHeadermap = 0; + }; + F5B5890C0156DC2201000001 = { + buildActionMask = 2147483647; + files = ( + F5B5890D0156DC2201000001, + F5B5890E0156DC2201000001, + F5B5890F0156DC2201000001, + ); + isa = PBXHeadersBuildPhase; + name = Headers; + }; + F5B5890D0156DC2201000001 = { + fileRef = F5B588FF0156DC2201000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B5890E0156DC2201000001 = { + fileRef = F5B589010156DC2201000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B5890F0156DC2201000001 = { + fileRef = F5B588FD0156DC2201000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B589100156DC2201000001 = { + buildActionMask = 2147483647; + files = ( + ); + isa = PBXResourcesBuildPhase; + name = "Bundle Resources"; + }; + F5B589110156DC2201000001 = { + buildActionMask = 2147483647; + files = ( + F5B589120156DC2201000001, + F5B589130156DC2201000001, + ); + isa = PBXSourcesBuildPhase; + name = Sources; + }; + F5B589120156DC2201000001 = { + fileRef = F5B589000156DC2201000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B589130156DC2201000001 = { + fileRef = F5B589020156DC2201000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B589140156DC2201000001 = { + buildActionMask = 2147483647; + files = ( + F5B589150156DC2201000001, + ); + isa = PBXFrameworksBuildPhase; + name = "Frameworks & Libraries"; + }; + F5B589150156DC2201000001 = { + fileRef = F5B5884A0156D40B01000001; + isa = PBXBuildFile; + settings = { + }; + }; + F5B589160156DC2201000001 = { + buildActionMask = 2147483647; + files = ( + ); + isa = PBXRezBuildPhase; + name = "ResourceManager Resources"; + }; + F5EBF6B801573EC201000001 = { + buildRules = ( + ); + buildSettings = { + COPY_PHASE_STRIP = NO; + }; + isa = PBXBuildStyle; + name = Development; + }; + F5EBF6B901573EC201000001 = { + buildRules = ( + ); + buildSettings = { + COPY_PHASE_STRIP = YES; + }; + isa = PBXBuildStyle; + name = Deployment; + }; + }; + rootObject = F5B5880F0156D2A601000001; +} diff --git a/Template Editor/Classes/Initalisation.cpp b/Template Editor/Classes/Initalisation.cpp new file mode 100644 index 0000000..1d8ec5d --- /dev/null +++ b/Template Editor/Classes/Initalisation.cpp @@ -0,0 +1 @@ +#include "Initalisation.h" #include "TemplateWindow.h" globals g; /*** INITALISE NEW EDITOR INSTANCE ***/ OSStatus Plug_InitInstance( Plug_PlugInRef plug, Plug_ResourceRef resource ) { // get system version OSStatus error = Gestalt( gestaltSystemVersion, &g.systemVersion ); if( error ) return error; // check appearance availablilty #if TARGET_API_MAC_CARBON g.useAppearance = true; #else ProcessSerialNumber psn; error = GetCurrentProcess( &psn ); if( error ) g.useAppearance = false; else g.useAppearance = IsAppearanceClient( &psn ); #endif // get template for resource ResType type = Host_GetResourceType( resource ); Handle tmpl = Host_GetDefaultTemplate( type ); SInt32 size = GetHandleSize( tmpl ); // cannot be less than 5 (string length of zero, four char code) if( tmpl == null || size < 5 ) return paramErr; // not the best error to return for this situation // create window Rect creationBounds; WindowRef window; SetRect( &creationBounds, 0, 0, kDefaultWindowWidth, kDefaultWindowHeight ); OffsetRect( &creationBounds, 8, 48 ); WindowAttributes attributes = kWindowStandardDocumentAttributes | kWindowStandardHandlerAttribute | kWindowInWindowMenuAttribute; if( g.systemVersion >= kMacOSX ) attributes |= kWindowLiveResizeAttribute; error = CreateNewWindow( kDocumentWindowClass, attributes, &creationBounds, &window ); Plug_WindowRef plugWindow = Host_RegisterWindow( plug, resource, window ); #if TARGET_API_MAC_CARBON // install window event handler EventHandlerRef ref = null; EventHandlerUPP handler = NewEventHandlerUPP( CarbonWindowEventHandler ); EventTypeSpec events[] = { { kEventClassWindow, kEventWindowClose } }; InstallWindowEventHandler( window, handler, GetEventTypeCount(events), events, plug, &ref ); #else ClassicEventHandlerUPP handler = NewClassicEventHandlerUPP( ClassicWindowEventHandler ); Host_InstallClassicWindowEventHandler( plugWindow, handler ); #endif // set window's background to default for theme if( g.useAppearance ) SetThemeWindowBackground( window, kThemeBrushModelessDialogBackgroundActive, false ); // cerate new template window class TemplateWindowPtr templateWindow = new TemplateWindow( window ); Host_SetWindowRefCon( plugWindow, (UInt32) templateWindow ); // parse the resource data Handle data = Host_GetResourceData( resource ); error = templateWindow->UseTemplate( tmpl ); // pass responsibility for disposing to the window error = templateWindow->ParseData( data ); // parses the resource into an array of Elements // show window ShowWindow( window ); SelectWindow( window ); return error; } /*** ELEMENT CONSTRUCTOR ***/ Element::Element( void ) { BlockZero( this, sizeof(Element) ); } /*** CARBON WINDOW EVENT HANDLER ***/ pascal OSStatus CarbonWindowEventHandler( EventHandlerCallRef handler, EventRef event, void *userData ) { #pragma unused( handler ) OSStatus error = eventNotHandledErr; Plug_PlugInRef plugRef = (Plug_PlugInRef) userData; WindowRef window = GetUserFocusWindow(); // overridden below for window class events // get event type UInt32 eventClass = GetEventClass( event ); UInt32 eventKind = GetEventKind( event ); // get event parameters if( eventClass == kEventClassWindow ) GetEventParameter( event, kEventParamDirectObject, typeWindowRef, null, sizeof(WindowRef), null, &window ); if( !window ) return error; Plug_WindowRef plugWindow = Host_GetPlugWindowFromWindowRef( window ); if( !plugWindow ) return error; TemplateWindowPtr templateWindow = (TemplateWindowPtr) Host_GetWindowRefCon( plugWindow ); if( !templateWindow ) return error; // get window rect Rect windowBounds; GetWindowPortBounds( window, &windowBounds ); // handle event static EventHandlerRef resizeEventHandlerRef = null; switch( eventClass ) { case kEventClassWindow: switch( eventKind ) { case kEventWindowClose: delete templateWindow; break; } break; } return error; } \ No newline at end of file diff --git a/Template Editor/Classes/Initalisation.h b/Template Editor/Classes/Initalisation.h new file mode 100644 index 0000000..5342c7c --- /dev/null +++ b/Template Editor/Classes/Initalisation.h @@ -0,0 +1 @@ +#include "Template Editor.h" pascal OSStatus CarbonWindowEventHandler( EventHandlerCallRef handler, EventRef event, void *userData ); \ No newline at end of file diff --git a/Template Editor/Classes/TemplateWindow.cpp b/Template Editor/Classes/TemplateWindow.cpp new file mode 100644 index 0000000..818c301 --- /dev/null +++ b/Template Editor/Classes/TemplateWindow.cpp @@ -0,0 +1 @@ +#include "TemplateWindow.h" /*** CREATOR ***/ TemplateWindow::TemplateWindow( WindowRef newWindow ) { // clear all instance bytes BlockZero( this, sizeof(TemplateWindow) ); // set the window to the one just created window = newWindow; } /*** DESTRUCTOR ***/ TemplateWindow::~TemplateWindow( void ) { ReadControls(); } /*** USE TEMPLATE ***/ OSStatus TemplateWindow::UseTemplate( Handle newTmpl ) { tmpl = newTmpl; return noErr; } /*** PARSE DATA ***/ OSStatus TemplateWindow::ParseData( Handle data ) { #pragma unused( data ) OSStatus error = noErr; error = ParseTemplate(); error = CreateControls(); return error; } /*** PARSE TEMPLATE ***/ OSStatus TemplateWindow::ParseTemplate( void ) { SInt32 size = GetHandleSize( tmpl ); SInt8 state = HGetState( tmpl ); HLock( tmpl ); ElementPtr current = elements = new Element; elementCount++; unsigned long position = 0; // start parsing at offset zero (unsurprisingly) while( position < size ) { // fill in element data short labelSize = *(*tmpl + position); BlockMoveData( *tmpl + position, ¤t->label, labelSize +1 ); position += labelSize +1; // don't forget length byte! BlockMoveData( *tmpl + position, ¤t->type, sizeof(ResType) ); position += sizeof(ResType); // but restype is always 4 bytes :) // create new element if( position < size ) { elementCount++; current->next = new Element; current = current->next; } } // clean up HSetState( tmpl, state ); return noErr; } /*** CREATE CONTROLS ***/ OSStatus TemplateWindow::CreateControls( void ) { OSStatus error = noErr; ControlRef root, label, secondaryLabel, group, radio, checkbox, edit, listcount; CreateRootControl( window, &root ); Rect windowBounds, rect, secondaryLabelRect; GetWindowPortBounds( window, &windowBounds ); // get resource data (the really tedious way - I ought to make this easier) Plug_WindowRef plugWindow = Host_GetPlugWindowFromWindowRef( window ); if( plugWindow == null ) DebugStr("\pplugWindow == null"); Plug_ResourceRef resource = Host_GetTargetResource( plugWindow ); if( resource == null ) DebugStr("\presource == null"); Handle data = Host_GetResourceData( resource ); SInt32 size = GetHandleSize( data ); SInt8 state = HGetState( data ); HLock( data ); // set control font style ControlID id; ControlFontStyleRec fontStyle, rectLabelStyle; fontStyle.flags = kControlUseFontMask + kControlUseJustMask; fontStyle.font = kControlFontSmallSystemFont; fontStyle.just = teJustLeft; rectLabelStyle.flags = kControlUseFontMask + kControlUseJustMask; rectLabelStyle.font = kControlFontSmallSystemFont; rectLabelStyle.just = teJustRight; // set up bounds for first control bounds.top = bounds.bottom = windowBounds.top; // bounds.top is ignored, bounds.bottom is the bottom of the previous control bounds.left = windowBounds.left +110 + 8; // bounds.left and .right are the sides of the controls excluding the label bounds.right = windowBounds.right - 8; // declare variables CFStringRef text = null; signed long position = 0; // offset of data currently being processed unsigned char boolBit = 0; ElementPtr current = elements; ElementPtr recursionOrigin = null; // bug: only handles one level of recursion signed long recursionTotal = 0; unsigned long recursionIndex = 0, recursionCount = 0; unsigned long m = 0, n = 0; // counters, one is always unique, the other is an element index for( ; m < elementCount; m++ ) { n++; // advance unique counter // create controls switch( current->type ) { case 'BOOL': // BOOL is two bytes long, false == 0x0000, true == 0x0100 { Boolean valid = false; if( size > position ) valid = (Boolean) ((unsigned short) *(*data + position)) > 0; SetRect( &rect, bounds.left, bounds.bottom +8, bounds.right, bounds.bottom + 2*16 +12 ); CreateRadioGroupControl( window, &rect, &group ); SetRect( &rect, rect.left, rect.top, rect.right, rect.top +16 ); error = CreateRadioButtonControl( window, &rect, CFSTR("False"), !valid, true, &radio ); // false is embedded at index zreo error = EmbedControl( radio, group ); OffsetRect( &rect, 0, 16 +4 ); error = CreateRadioButtonControl( window, &rect, CFSTR("True"), valid, true, &radio ); // true is embedded at index one error = EmbedControl( radio, group ); SetControlValue( group, valid +1 ); SetRect( &rect, bounds.left, bounds.bottom +8, bounds.right, bounds.bottom + 2*16 +12 ); rect.bottom += 4; position += 2; // set control ID id.id = n; id.signature = kBooleanGroupSignature; SetControlID( group, &id ); } break; case 'BBIT': case 'FBIT': if( boolBit == 0 ) { // greate group box SetRect( &rect, bounds.left, bounds.bottom +8, bounds.right, bounds.bottom + 20*8 +32 ); CreateGroupBoxControl( window, &rect, CFSTR("Some bits to click"), true, &group ); bounds.bottom += 24; // set control ID id.id = n; id.signature = kBooleanGroupSignature; SetControlID( group, &id ); } else { id.id = n - boolBit; id.signature = kBooleanGroupSignature; GetControlByID( window, &id, &group ); } // create checkbox { SInt32 content = 0x00000000; if( size > position && current->type == 'BBIT' ) content = ((*(char*)(*data + position) << boolBit) & 0x80)? 1:0; SetRect( &rect, bounds.left +8, bounds.bottom +4, bounds.right -8, bounds.bottom +20 ); text = CFStringCreateWithPascalString( CFAllocatorGetDefault(), current->label, kCFStringEncodingMacRoman ); CreateCheckBoxControl( window, &rect, text, content, true, &checkbox ); EmbedControl( checkbox, group ); // checkboxes are embedded from 0 to 7, in direct bit order if( current->type == 'FBIT' ) HiliteControl( checkbox, kControlDisabledPart ); // kControlInactivePart might work too // DisableControl( checkbox ); // DeactivateControl( checkbox ); // HideControl( checkbox ); // advance BBIT counter boolBit += 1; if( boolBit == 8 ) { boolBit = 0; position += 1; rect.bottom += 12; } } break; case 'DBYT': { Str255 number; SInt8 content = 0x00; if( size > position ) content = *((char*)(*data + position)); NumToString( (long) content, number ); text = CFStringCreateWithPascalString( CFAllocatorGetDefault(), number, kCFStringEncodingMacRoman ); SetRect( &rect, bounds.left, bounds.bottom +8, bounds.right, bounds.bottom +22 ); CreateEditTextControl( window, &rect, text, false, true, &fontStyle, &edit ); rect.bottom += 4; position += 1; // set control ID id.id = n; id.signature = kEditFieldSignature; SetControlID( edit, &id ); } break; case 'DWRD': { Str255 number; SInt16 content = 0x0000; if( size > position +1 ) content = *((short*)(*data + position)); NumToString( (long) content, number ); text = CFStringCreateWithPascalString( CFAllocatorGetDefault(), number, kCFStringEncodingMacRoman ); SetRect( &rect, bounds.left, bounds.bottom +8, bounds.right, bounds.bottom +22 ); CreateEditTextControl( window, &rect, text, false, true, &fontStyle, &edit ); rect.bottom += 4; position += 2; // set control ID id.id = n; id.signature = kEditFieldSignature; SetControlID( edit, &id ); } break; case 'DLNG': { Str255 number; SInt32 content = 0x00000000; if( size > position +3 ) content = *((long*)(*data + position)); NumToString( (long) content, number ); text = CFStringCreateWithPascalString( CFAllocatorGetDefault(), number, kCFStringEncodingMacRoman ); SetRect( &rect, bounds.left, bounds.bottom +8, bounds.right, bounds.bottom +22 ); CreateEditTextControl( window, &rect, text, false, true, &fontStyle, &edit ); rect.bottom += 4; position += 4; // set control ID id.id = n; id.signature = kEditFieldSignature; SetControlID( edit, &id ); } break; case 'HBYT': { Str255 number; SInt8 content = 0x00; if( size > position ) content = *((char*)(*data + position)); NumToString( (long) content, number ); // convert to hex here text = CFStringCreateWithPascalString( CFAllocatorGetDefault(), number, kCFStringEncodingMacRoman ); SetRect( &rect, bounds.left, bounds.bottom +8, bounds.right, bounds.bottom +22 ); CreateEditTextControl( window, &rect, text, false, true, &fontStyle, &edit ); rect.bottom += 4; position += 1; // set control ID id.id = n; id.signature = kEditFieldSignature; SetControlID( edit, &id ); // add dollar denoting hex SetRect( &secondaryLabelRect, bounds.left -24, bounds.bottom +9, bounds.left -6, bounds.bottom +22 ); CreateStaticTextControl( window, &secondaryLabelRect, CFSTR("$"), &rectLabelStyle, &secondaryLabel ); } break; case 'HWRD': { Str255 number; SInt16 content = 0x0000; if( size > position +1 ) content = *((short*)(*data + position)); NumToString( (long) content, number ); // convert to hex here text = CFStringCreateWithPascalString( CFAllocatorGetDefault(), number, kCFStringEncodingMacRoman ); SetRect( &rect, bounds.left, bounds.bottom +8, bounds.right, bounds.bottom +21 ); CreateEditTextControl( window, &rect, text, false, true, &fontStyle, &edit ); rect.bottom += 4; position += 2; // set control ID id.id = n; id.signature = kEditFieldSignature; SetControlID( edit, &id ); // add dollar denoting hex SetRect( &secondaryLabelRect, bounds.left -24, bounds.bottom +9, bounds.left -6, bounds.bottom +22 ); CreateStaticTextControl( window, &secondaryLabelRect, CFSTR("$"), &rectLabelStyle, &secondaryLabel ); } break; case 'HLNG': { Str255 number; SInt32 content = 0x00000000; if( size > position +3 ) content = *((long*)(*data + position)); NumToString( (long) content, number ); // convert to hex here text = CFStringCreateWithPascalString( CFAllocatorGetDefault(), number, kCFStringEncodingMacRoman ); SetRect( &rect, bounds.left, bounds.bottom +8, bounds.right, bounds.bottom +21 ); CreateEditTextControl( window, &rect, text, false, true, &fontStyle, &edit ); rect.bottom += 4; position += 4; // set control ID id.id = n; id.signature = kEditFieldSignature; SetControlID( edit, &id ); // add dollar denoting hex SetRect( &secondaryLabelRect, bounds.left -24, bounds.bottom +9, bounds.left -6, bounds.bottom +22 ); CreateStaticTextControl( window, &secondaryLabelRect, CFSTR("$"), &rectLabelStyle, &secondaryLabel ); } break; case 'FBYT': position += 1; break; case 'FWRD': position += 2; break; case 'FLNG': position += 4; break; case 'AWRD': position += position % 2; break; case 'ALNG': position += position % 4; break; case 'CHAR': // one byte { UInt8 content = 0x00; if( size > position ) content = *((unsigned char*)(*data + position)); text = CFStringCreateWithBytes( CFAllocatorGetDefault(), (unsigned char *) &content, 1, kCFStringEncodingMacRoman, false ); SetRect( &rect, bounds.left, bounds.bottom +8, bounds.right, bounds.bottom +22 ); CreateEditTextControl( window, &rect, text, false, true, &fontStyle, &edit ); rect.bottom += 4; position += 1; // set control ID id.id = n; id.signature = kEditFieldSignature; SetControlID( edit, &id ); } break; case 'TNAM': // four bytes { UInt32 content = 0x00000000; if( size > position +3 ) content = *((unsigned long*)(*data + position)); text = CFStringCreateWithBytes( CFAllocatorGetDefault(), (unsigned char *) &content, 4, kCFStringEncodingMacRoman, false ); SetRect( &rect, bounds.left, bounds.bottom +8, bounds.right, bounds.bottom +22 ); CreateEditTextControl( window, &rect, text, false, true, &fontStyle, &edit ); rect.bottom += 4; position += 4; // set control ID id.id = n; id.signature = kEditFieldSignature; SetControlID( edit, &id ); } break; case 'CSTR': // one to 32,768 bytes? { SetRect( &rect, bounds.left, bounds.bottom +8, bounds.right, bounds.bottom +22 ); if( size > position ) // bug: if the bytes do not end in a 0x00, the function just keeps on reading! text = CFStringCreateWithCString( CFAllocatorGetDefault(), (char *) (*data + position), kCFStringEncodingMacRoman ); else text = CFSTR(""); CreateEditTextControl( window, &rect, text, false, true, &fontStyle, &edit ); position += CFStringGetLength(text) +1; rect.bottom += 4; // set control ID id.id = n; id.signature = kEditFieldSignature; SetControlID( edit, &id ); } break; case 'ECST': // one to 32,768 bytes? { SetRect( &rect, bounds.left, bounds.bottom +8, bounds.right, bounds.bottom +22 ); if( size > position ) text = CFStringCreateWithCString( CFAllocatorGetDefault(), (char *) (*data + position), kCFStringEncodingMacRoman ); else text = CFSTR(""); CreateEditTextControl( window, &rect, text, false, true, &fontStyle, &edit ); position += CFStringGetLength(text) +1; // null byte position += (CFStringGetLength(text) % 2 == 0)? 1:0; // to pad or not to pad? rect.bottom += 4; // set control ID id.id = n; id.signature = kEditFieldSignature; SetControlID( edit, &id ); } break; case 'OCST': // one to 32,768 bytes? { SetRect( &rect, bounds.left, bounds.bottom +8, bounds.right, bounds.bottom +22 ); if( size > position ) text = CFStringCreateWithCString( CFAllocatorGetDefault(), (char *) (*data + position), kCFStringEncodingMacRoman ); else text = CFSTR(""); CreateEditTextControl( window, &rect, text, false, true, &fontStyle, &edit ); position += CFStringGetLength(text) +1; // null byte position += (CFStringGetLength(text) % 2 == 1)? 1:0; // to pad or not to pad? rect.bottom += 4; // set control ID id.id = n; id.signature = kEditFieldSignature; SetControlID( edit, &id ); } break; case 'PSTR': // one to 256 bytes { unsigned char length = 0; if( size > position ) length = *(*data + position); SetRect( &rect, bounds.left, bounds.bottom +8, bounds.right, bounds.bottom +22 ); if( size > position ) text = CFStringCreateWithPascalString( CFAllocatorGetDefault(), (unsigned char *) (*data + position), kCFStringEncodingMacRoman ); else text = CFSTR(""); CreateEditTextControl( window, &rect, text, false, true, &fontStyle, &edit ); position += length +1; rect.bottom += 4; // set control ID id.id = n; id.signature = kEditFieldSignature; SetControlID( edit, &id ); } break; case 'ESTR': // one to 256 bytes { unsigned char length = 0; if( size > position ) length = *(*data + position); SetRect( &rect, bounds.left, bounds.bottom +8, bounds.right, bounds.bottom +22 ); if( size > position ) text = CFStringCreateWithPascalString( CFAllocatorGetDefault(), (unsigned char *) (*data + position), kCFStringEncodingMacRoman ); else text = CFSTR(""); CreateEditTextControl( window, &rect, text, false, true, &fontStyle, &edit ); position += length +1; position += (length % 2 == 0)? 1:0; rect.bottom += 4; // set control ID id.id = n; id.signature = kEditFieldSignature; SetControlID( edit, &id ); } break; case 'OSTR': // one to 256 bytes { unsigned char length = 0; if( size > position ) length = *(*data + position); SetRect( &rect, bounds.left, bounds.bottom +8, bounds.right, bounds.bottom +22 ); if( size > position ) text = CFStringCreateWithPascalString( CFAllocatorGetDefault(), (unsigned char *) (*data + position), kCFStringEncodingMacRoman ); else text = CFSTR(""); CreateEditTextControl( window, &rect, text, false, true, &fontStyle, &edit ); position += length +1; position += (length % 2 == 1)? 1:0; rect.bottom += 4; // set control ID id.id = n; id.signature = kEditFieldSignature; SetControlID( edit, &id ); } break; case 'WSTR': // one to 65,536 bytes { unsigned short length = 0; if( size > position +1 ) BlockMoveData( *data + position, &length, 2 ); // length = *((unsigned short*) *data + position); SetRect( &rect, bounds.left, bounds.bottom +8, bounds.right, bounds.bottom +22 ); if( size > position +1 ) text = CFStringCreateWithBytes( CFAllocatorGetDefault(), (unsigned char *) (*data + position +2), (CFIndex) length, kCFStringEncodingMacRoman, false ); else text = CFSTR(""); CreateEditTextControl( window, &rect, text, false, true, &fontStyle, &edit ); position += length +2; rect.bottom += 4; // set control ID id.id = n; id.signature = kEditFieldSignature; SetControlID( edit, &id ); } break; case 'LSTR': // one to 4,294,967,296 bytes { unsigned long length = 0; if( size > position +3 ) BlockMoveData( *data + position, &length, 4 ); // length = *((unsigned long*) *data + position); SetRect( &rect, bounds.left, bounds.bottom +8, bounds.right, bounds.bottom +22 ); if( size > position +3 ) text = CFStringCreateWithBytes( CFAllocatorGetDefault(), (unsigned char *) (*data + position +4), (CFIndex) length, kCFStringEncodingMacRoman, false ); else text = CFSTR(""); CreateEditTextControl( window, &rect, text, false, true, &fontStyle, &edit ); position += length +4; rect.bottom += 4; // set control ID id.id = n; id.signature = kEditFieldSignature; SetControlID( edit, &id ); } break; case 'HEXD': // bug: should display in hex - wait until hex is implemented :-) // infinetly long (limited only by CFString) { SetRect( &rect, bounds.left, bounds.bottom +8, bounds.right, bounds.bottom +22 ); if( size > position ) text = CFStringCreateWithBytes( CFAllocatorGetDefault(), (unsigned char *) (*data + position), size - position, kCFStringEncodingMacRoman, false ); else text = CFSTR(""); CreateEditTextControl( window, &rect, text, false, true, &fontStyle, &edit ); rect.bottom += 4; position = size; // bug: should ensure termination of parsing // set control ID id.id = n; id.signature = kHexDumpSignature; SetControlID( edit, &id ); } break; case 'RECT': // four SInt16s: top, left, bottom, right { // greate group box SInt16 menuID = 2001; // bug: make into a constant SInt16 titleWidth = 0; SInt16 titleJustification = teJustLeft; Style titleStyle = normal; SetRect( &rect, bounds.left, bounds.bottom +8, bounds.right, bounds.bottom + 26*4 +40 ); error = CreatePopupGroupBoxControl( window, &rect, CFSTR("A Rectangle"), true, menuID, true, titleWidth, titleJustification, titleStyle, &group ); // set control ID id.id = n; id.signature = kRectGroupSignature; SetControlID( group, &id ); // create top { Str255 number; SInt16 content = 0x0000; if( size > position +1 ) content = *((short*)(*data + position)); NumToString( (long) content, number ); text = CFStringCreateWithPascalString( CFAllocatorGetDefault(), number, kCFStringEncodingMacRoman ); SetRect( &rect, bounds.left + (bounds.right - bounds.left -32) /2 +8, bounds.bottom +40, bounds.right -12, bounds.bottom +54 ); CreateEditTextControl( window, &rect, text, false, true, &fontStyle, &edit ); EmbedControl( edit, group ); // embed order: top, label, left, label, bottom, label, right, label SetRect( &secondaryLabelRect, bounds.left +12, bounds.bottom +40, bounds.left + (bounds.right - bounds.left -32) /2 -8, bounds.bottom +54 ); text = CFStringCreateWithPascalString( CFAllocatorGetDefault(), "\pTop", kCFStringEncodingMacRoman ); CreateStaticTextControl( window, &secondaryLabelRect, text, &rectLabelStyle, &secondaryLabel ); EmbedControl( secondaryLabel, group ); } position += 2; // create left { Str255 number; SInt16 content = 0x0000; if( size > position +1 ) content = *((short*)(*data + position)); NumToString( (long) content, number ); text = CFStringCreateWithPascalString( CFAllocatorGetDefault(), number, kCFStringEncodingMacRoman ); OffsetRect( &rect, 0, 26 ); CreateEditTextControl( window, &rect, text, false, true, &fontStyle, &edit ); EmbedControl( edit, group ); // embed order: top, label, left, label, bottom, label, right, label OffsetRect( &secondaryLabelRect, 0, 26 ); text = CFStringCreateWithPascalString( CFAllocatorGetDefault(), "\pLeft", kCFStringEncodingMacRoman ); CreateStaticTextControl( window, &secondaryLabelRect, text, &rectLabelStyle, &secondaryLabel ); EmbedControl( secondaryLabel, group ); } position += 2; // create bottom { Str255 number; SInt16 content = 0x0000; if( size > position +1 ) content = *((short*)(*data + position)); NumToString( (long) content, number ); text = CFStringCreateWithPascalString( CFAllocatorGetDefault(), number, kCFStringEncodingMacRoman ); OffsetRect( &rect, 0, 26 ); CreateEditTextControl( window, &rect, text, false, true, &fontStyle, &edit ); EmbedControl( edit, group ); // embed order: top, label, left, label, bottom, label, right, label OffsetRect( &secondaryLabelRect, 0, 26 ); text = CFStringCreateWithPascalString( CFAllocatorGetDefault(), "\pBottom", kCFStringEncodingMacRoman ); CreateStaticTextControl( window, &secondaryLabelRect, text, &rectLabelStyle, &secondaryLabel ); EmbedControl( secondaryLabel, group ); } position += 2; // create right { Str255 number; SInt16 content = 0x0000; if( size > position +1 ) content = *((short*)(*data + position)); NumToString( (long) content, number ); text = CFStringCreateWithPascalString( CFAllocatorGetDefault(), number, kCFStringEncodingMacRoman ); OffsetRect( &rect, 0, 26 ); CreateEditTextControl( window, &rect, text, false, true, &fontStyle, &edit ); EmbedControl( edit, group ); // embed order: top, label, left, label, bottom, label, right, label OffsetRect( &secondaryLabelRect, 0, 26 ); text = CFStringCreateWithPascalString( CFAllocatorGetDefault(), "\pRight", kCFStringEncodingMacRoman ); CreateStaticTextControl( window, &secondaryLabelRect, text, &rectLabelStyle, &secondaryLabel ); EmbedControl( secondaryLabel, group ); } position += 2; rect.top = bounds.bottom +14; rect.bottom += 18; } break; case 'ZCNT': { Str255 number; SInt16 content = 0x0000; if( size > position +1 ) content = *((short*)(*data + position)) +1; // add one for zero-based count NumToString( (long) content, number ); text = CFStringCreateWithPascalString( CFAllocatorGetDefault(), number, kCFStringEncodingMacRoman ); SetRect( &rect, bounds.left, bounds.bottom +8, bounds.right, bounds.bottom +22 ); CreateStaticTextControl( window, &rect, text, &fontStyle, &listcount ); rect.bottom += 4; position += 2; // set recursion total (number of times to repeat) recursionTotal = content; // set control ID id.id = n; id.signature = kListCountSignature; SetControlID( edit, &id ); } break; case 'OCNT': { Str255 number; SInt16 content = 0x0000; if( size > position +1 ) content = *((short*)(*data + position)); NumToString( (long) content, number ); text = CFStringCreateWithPascalString( CFAllocatorGetDefault(), number, kCFStringEncodingMacRoman ); SetRect( &rect, bounds.left, bounds.bottom +8, bounds.right, bounds.bottom +22 ); CreateStaticTextControl( window, &rect, text, &fontStyle, &listcount ); rect.bottom += 4; position += 2; // set recursion total (number of times to repeat) recursionTotal = content; // set control ID id.id = n; id.signature = kListCountSignature; SetControlID( edit, &id ); } break; case 'LSTB': // repeats to end of resource { recursionTotal = -1; recursionIndex = m; recursionOrigin = current; SetRect( &rect, bounds.left, bounds.bottom +8, bounds.right, bounds.bottom +22 ); rect.bottom += 4; } break; case 'LSTC': // repeats recursionTotal times { recursionIndex = m; recursionOrigin = current; SetRect( &rect, bounds.left, bounds.bottom +8, bounds.right, bounds.bottom +22 ); rect.bottom += 4; } break; case 'LSTZ': // repeats until 0x00 is encountered as first byte of next block { recursionTotal = -2; recursionIndex = m; recursionOrigin = current; SetRect( &rect, bounds.left, bounds.bottom +8, bounds.right, bounds.bottom +22 ); rect.bottom += 4; } break; case 'LSTE': { recursionCount++; if( recursionTotal == -2 ) { UInt8 content = 0x00; if( size > position ) content = *((unsigned char*)(*data + position)); if( content != 0x00 ) { // set current parser back to where recursion began from m = recursionIndex; current = recursionOrigin; } } else if( recursionTotal == -1 ) { if( size > position ) { // set current parser back to where recursion began from m = recursionIndex; current = recursionOrigin; } } else if( recursionCount < recursionTotal ) { // set current parser back to where recursion began from m = recursionIndex; current = recursionOrigin; } SetRect( &rect, bounds.left, bounds.bottom +8, bounds.right, bounds.bottom +22 ); rect.bottom += 4; } break; /* default: SetRect( &rect, bounds.left, bounds.bottom +8, bounds.right, bounds.bottom +22 ); if( current->type & 0xFF000000 == 'P' << 24 ) { // do Pxxx string SInt32 length; Str255 number; number[0] = 3; number[1] = (char) current->type << 8; number[2] = (char) current->type << 16; number[3] = (char) current->type << 24; StringToNum( number, &length ); if( size > position ) text = CFStringCreateWithPascalString( CFAllocatorGetDefault(), (unsigned char *) (*data + position), kCFStringEncodingMacRoman ); else text = CFSTR(""); CreateStaticTextControl( window, &rect, text, &fontStyle, &edit ); position += length + 1; // set control ID id.id = n; id.signature = kEditFieldSignature; SetControlID( edit, &id ); } else if( current->type & 0xFF000000 == 'C' << 24 ) { // do Cxxx string SInt32 length; Str255 number; number[0] = 3; number[1] = (char) current->type << 8; number[2] = (char) current->type << 16; number[3] = (char) current->type << 24; StringToNum( number, &length ); if( size > position ) text = CFStringCreateWithCString( CFAllocatorGetDefault(), *data + position, kCFStringEncodingMacRoman ); else text = CFSTR(""); CreateStaticTextControl( window, &rect, text, &fontStyle, &edit ); position += length + 1; // set control ID id.id = n; id.signature = kEditFieldSignature; SetControlID( edit, &id ); } else if( current->type & 0xFF000000 == 'H' << 24 ) { // do Hxxx string } // else I don't know what it is. rect.bottom += 4; break; */ } // create label if( (current->type != 'AWRD') && (current->type != 'ALNG') && ((current->type & 0xFF000000) != ('F' << 24)) ) // skip aligns and fillers { SetRect( &rect, windowBounds.left +8, rect.top, windowBounds.left +100, rect.bottom ); text = CFStringCreateWithPascalString( CFAllocatorGetDefault(), current->label, kCFStringEncodingMacRoman ); // Host_DebugError( current->label, rect.top ); CreateStaticTextControl( window, &rect, text, &fontStyle, &label ); id.id = n; id.signature = kLabelSignature; SetControlID( label, &id ); } // save rect for next element to use bounds.top = bounds.bottom = rect.bottom; // advance current element current = current->next; } if( current != null ) Host_DebugError( "\pLast elementÕs 'next' parameter != 0", (long) current ); // last element should have null as it's "next" param HSetState( data, state ); return error; } /*** READ CONTROLS ***/ OSStatus TemplateWindow::ReadControls( void ) { OSStatus error = noErr; // get resource ref Plug_WindowRef plugWindow = Host_GetPlugWindowFromWindowRef( window ); if( plugWindow == null ) DebugStr("\pplugWindow == null"); Plug_ResourceRef resource = Host_GetTargetResource( plugWindow ); if( resource == null ) DebugStr("\presource == null"); // create blank resource UInt32 size = 0; Handle data = NewHandle(0); // cycle through each element of the template ControlID id; ControlRef control, group; ElementPtr current = elements; ElementPtr recursionOrigin = null; // bug: only handles one level of recursion signed long recursionTotal = 0; unsigned long recursionIndex = 0, recursionCount = 0; unsigned long m = 0, n = 0; // counters, one is always unique, the other is an element index for( ; m < elementCount; m++ ) { n++; // advance unique counter // read controls switch( current->type ) { case 'BOOL': // BOOL is two bytes long, false == 0x0000, true == 0x0100 { UInt16 value = 0; // get control value id.id = n; id.signature = kBooleanGroupSignature; error = GetControlByID( window, &id, &control ); if( !error ) value = GetControlValue( control ) -1; // false gives 1, true gives 2 value <<= 8; // append correct data to handle SInt8 state = HGetState( data ); SetHandleSize( data, size +2 ); if( !MemError() ) { HLock( data ); // lock handle after increasing it's size BlockMoveData( &value, *data + size, 2 ); HSetState( data, state ); } size += 2; } break; case 'BBIT': case 'FBIT': { // read all eight bits in simultaneously // nb. re. filler bits - the control exists but is disabled/hidden. reading it gives value of 0 id.id = n; id.signature = kBooleanGroupSignature; error = GetControlByID( window, &id, &group ); if( !error ) { // read checkboxs UInt8 value = 0x00; GetIndexedSubControl( group, 1, &control ); value += (GetControlValue( control ) & 0x01) << 7; GetIndexedSubControl( group, 2, &control ); value += (GetControlValue( control ) & 0x01) << 6; GetIndexedSubControl( group, 3, &control ); value += (GetControlValue( control ) & 0x01) << 5; GetIndexedSubControl( group, 4, &control ); value += (GetControlValue( control ) & 0x01) << 4; GetIndexedSubControl( group, 5, &control ); value += (GetControlValue( control ) & 0x01) << 3; GetIndexedSubControl( group, 6, &control ); value += (GetControlValue( control ) & 0x01) << 2; GetIndexedSubControl( group, 7, &control ); value += (GetControlValue( control ) & 0x01) << 1; GetIndexedSubControl( group, 8, &control ); value += (GetControlValue( control ) & 0x01) << 0; // append correct data to handle SInt8 state = HGetState( data ); SetHandleSize( data, size +1 ); if( !MemError() ) { HLock( data ); // lock handle after increasing it's size BlockMoveData( &value, *data + size, 1 ); HSetState( data, state ); } } // update size & advance element counter size += 1; n += 7; } break; case 'DBYT': { id.id = n; id.signature = kEditFieldSignature; error = GetControlByID( window, &id, &control ); if( !error ) { // find out how much text has been typed Size bufferSize, actualSize; error = GetControlDataSize( control, kControlEditTextPart, kControlEditTextTextTag, &bufferSize ); if( !error ) { // allocate buffer and read data Ptr buffer = NewPtrClear( bufferSize ); error = GetControlData( control, kControlEditTextPart, kControlEditTextTextTag, bufferSize, buffer, &actualSize ); if( !error ) { // convert buffer into desired format SInt8 value; SInt32 number; Str255 string; string[0] = actualSize; BlockMoveData( buffer, &string[1], actualSize ); StringToNum( string, &number ); value = (char) number; // append correct data to handle SInt8 state = HGetState( data ); SetHandleSize( data, size +1 ); if( !MemError() ) { HLock( data ); // lock handle after increasing it's size BlockMoveData( &value, *data + size, 1 ); HSetState( data, state ); } size += 1; } DisposePtr( buffer ); } } } break; case 'DWRD': { id.id = n; id.signature = kEditFieldSignature; error = GetControlByID( window, &id, &control ); if( !error ) { // find out how much text has been typed Size bufferSize, actualSize; error = GetControlDataSize( control, kControlEditTextPart, kControlEditTextTextTag, &bufferSize ); if( !error ) { // allocate buffer and read data Ptr buffer = NewPtrClear( bufferSize ); error = GetControlData( control, kControlEditTextPart, kControlEditTextTextTag, bufferSize, buffer, &actualSize ); if( !error ) { // convert buffer into desired format SInt16 value; SInt32 number; Str255 string; string[0] = actualSize; BlockMoveData( buffer, &string[1], actualSize ); StringToNum( string, &number ); value = (short) number; // append correct data to handle SInt8 state = HGetState( data ); SetHandleSize( data, size +2 ); error = MemError(); if( !error ) { HLock( data ); // lock handle after increasing it's size BlockMoveData( &value, *data + size, 2 ); Host_DebugError( "\pvalue", value ); HSetState( data, state ); } else Host_DebugError( "\pMemError() occoured", error ); size += 2; } else Host_DebugError( "\pGetControlData() failed", error ); DisposePtr( buffer ); } else Host_DebugError( "\pGetControlDataSize() failed", error ); } else Host_DebugError( "\pCould not get EditText control", error ); } break; case 'DLNG': { id.id = n; id.signature = kEditFieldSignature; error = GetControlByID( window, &id, &control ); if( !error ) { // find out how much text has been typed Size bufferSize, actualSize; error = GetControlDataSize( control, kControlEditTextPart, kControlEditTextTextTag, &bufferSize ); if( !error ) { // allocate buffer and read data Ptr buffer = NewPtrClear( bufferSize ); error = GetControlData( control, kControlEditTextPart, kControlEditTextTextTag, bufferSize, buffer, &actualSize ); if( !error ) { // convert buffer into desired format SInt32 value; Str255 string; string[0] = actualSize; BlockMoveData( buffer, &string[1], actualSize ); StringToNum( string, &value ); // append correct data to handle SInt8 state = HGetState( data ); SetHandleSize( data, size +4 ); if( !MemError() ) { HLock( data ); // lock handle after increasing it's size BlockMoveData( &value, *data + size, 4 ); HSetState( data, state ); } size += 4; } DisposePtr( buffer ); } } } break; case 'HBYT': { id.id = n; id.signature = kEditFieldSignature; error = GetControlByID( window, &id, &control ); if( !error ) { // find out how much text has been typed Size bufferSize, actualSize; error = GetControlDataSize( control, kControlEditTextPart, kControlEditTextTextTag, &bufferSize ); if( !error ) { // allocate buffer and read data Ptr buffer = NewPtrClear( bufferSize ); error = GetControlData( control, kControlEditTextPart, kControlEditTextTextTag, bufferSize, buffer, &actualSize ); if( !error ) { // convert buffer into desired format SInt8 value; SInt32 number; Str255 string; string[0] = actualSize; BlockMoveData( buffer, &string[1], actualSize ); StringToNum( string, &number ); value = (char) number; // append correct data to handle SInt8 state = HGetState( data ); SetHandleSize( data, size +1 ); if( !MemError() ) { HLock( data ); // lock handle after increasing it's size BlockMoveData( &value, *data + size, 1 ); HSetState( data, state ); } size += 1; } DisposePtr( buffer ); } } } break; case 'HWRD': { id.id = n; id.signature = kEditFieldSignature; error = GetControlByID( window, &id, &control ); if( !error ) { // find out how much text has been typed Size bufferSize, actualSize; error = GetControlDataSize( control, kControlEditTextPart, kControlEditTextTextTag, &bufferSize ); if( !error ) { // allocate buffer and read data Ptr buffer = NewPtrClear( bufferSize ); error = GetControlData( control, kControlEditTextPart, kControlEditTextTextTag, bufferSize, buffer, &actualSize ); if( !error ) { // convert buffer into desired format SInt16 value; SInt32 number; Str255 string; string[0] = actualSize; BlockMoveData( buffer, &string[1], actualSize ); StringToNum( string, &number ); value = (short) number; // append correct data to handle SInt8 state = HGetState( data ); SetHandleSize( data, size +2 ); if( !MemError() ) { HLock( data ); // lock handle after increasing it's size BlockMoveData( &value, *data + size, 2 ); HSetState( data, state ); } size += 2; } DisposePtr( buffer ); } } } break; case 'HLNG': { id.id = n; id.signature = kEditFieldSignature; error = GetControlByID( window, &id, &control ); if( !error ) { // find out how much text has been typed Size bufferSize, actualSize; error = GetControlDataSize( control, kControlEditTextPart, kControlEditTextTextTag, &bufferSize ); if( !error ) { // allocate buffer and read data Ptr buffer = NewPtrClear( bufferSize ); error = GetControlData( control, kControlEditTextPart, kControlEditTextTextTag, bufferSize, buffer, &actualSize ); if( !error ) { // convert buffer into desired format SInt32 value; Str255 string; string[0] = actualSize; BlockMoveData( buffer, &string[1], actualSize ); StringToNum( string, &value ); // append correct data to handle SInt8 state = HGetState( data ); SetHandleSize( data, size +4 ); if( !MemError() ) { HLock( data ); // lock handle after increasing it's size BlockMoveData( &value, *data + size, 4 ); HSetState( data, state ); } size += 4; } DisposePtr( buffer ); } } } break; case 'FBYT': { SInt8 state = HGetState( data ); SetHandleSize( data, size +1 ); if( !MemError() ) { HLock( data ); // lock handle after increasing it's size *((char *) *data + size) = 0x00; HSetState( data, state ); } size += 1; } break; case 'FWRD': { SInt16 value = 0; SInt8 state = HGetState( data ); SetHandleSize( data, size +2 ); if( !MemError() ) { HLock( data ); // lock handle after increasing it's size BlockMoveData( &value, *data + size, 2 ); // *((short *) *data + size) = 0x0000; HSetState( data, state ); } size += 2; } break; case 'FLNG': { SInt32 value = 0; SInt8 state = HGetState( data ); SetHandleSize( data, size +4 ); if( !MemError() ) { HLock( data ); // lock handle after increasing it's size BlockMoveData( &value, *data + size, 4 ); // *((long *) *data + size) = 0x00000000; HSetState( data, state ); } size += 4; } break; case 'AWRD': { SInt8 state = HGetState( data ); SetHandleSize( data, size + size % 2 ); if( !MemError() ) { HLock( data ); // lock handle after increasing it's size while( size % 2 ) { *((char *) *data + size) = 0x00; size++; } HSetState( data, state ); } } break; case 'ALNG': { SInt8 state = HGetState( data ); SetHandleSize( data, size + size % 4 ); if( !MemError() ) { HLock( data ); // lock handle after increasing it's size while( size % 4 ) { *((char *) *data + size) = 0x00; size++; } HSetState( data, state ); } } break; case 'CHAR': // one byte { id.id = n; id.signature = kEditFieldSignature; error = GetControlByID( window, &id, &control ); if( !error ) { // find out how much text has been typed Size bufferSize, actualSize; error = GetControlDataSize( control, kControlEditTextPart, kControlEditTextTextTag, &bufferSize ); if( !error ) { // allocate buffer and read data Ptr buffer = NewPtrClear( (bufferSize > 1)? bufferSize:1 ); // ensures minimum buffer of one byte error = GetControlData( control, kControlEditTextPart, kControlEditTextTextTag, bufferSize, buffer, &actualSize ); if( !error ) { // put zero into buffer if actualSize == 0 if( actualSize < 1 ) *((char *) buffer) = 0x00; // append correct data to handle SInt8 state = HGetState( data ); SetHandleSize( data, size +1 ); if( !MemError() ) { HLock( data ); // lock handle after increasing it's size BlockMoveData( buffer, *data + size, 1 ); HSetState( data, state ); } size += 1; } DisposePtr( buffer ); } } } break; case 'TNAM': // four bytes { id.id = n; id.signature = kEditFieldSignature; error = GetControlByID( window, &id, &control ); if( !error ) { // find out how much text has been typed Size bufferSize, actualSize; error = GetControlDataSize( control, kControlEditTextPart, kControlEditTextTextTag, &bufferSize ); if( !error ) { // allocate buffer and read data Ptr buffer = NewPtrClear( (bufferSize > 4)? bufferSize:4 ); // ensures minimum buffer of four bytes error = GetControlData( control, kControlEditTextPart, kControlEditTextTextTag, bufferSize, buffer, &actualSize ); Str255 text; text[0] = 4; BlockMoveData( buffer, &text[1], 4 ); Host_DebugError( text, bufferSize ); if( !error ) { // put zero into buffer if actualSize == 0 if( actualSize < 4 ) *((long *) buffer) = 0x00000000; // append correct data to handle SInt8 state = HGetState( data ); SetHandleSize( data, size +4 ); if( !MemError() ) { HLock( data ); // lock handle after increasing it's size BlockMoveData( buffer, *data + size, 4 ); HSetState( data, state ); } size += 4; } DisposePtr( buffer ); } } } break; case 'CSTR': { id.id = n; id.signature = kEditFieldSignature; error = GetControlByID( window, &id, &control ); if( !error ) { // find out how much text has been typed Size bufferSize, actualSize; error = GetControlDataSize( control, kControlEditTextPart, kControlEditTextTextTag, &bufferSize ); if( !error ) { // allocate buffer and read data Ptr buffer = NewPtrClear( bufferSize ); error = GetControlData( control, kControlEditTextPart, kControlEditTextTextTag, bufferSize, buffer, &actualSize ); if( !error ) { // append correct data to handle SInt8 state = HGetState( data ); SetHandleSize( data, size + actualSize +1 ); if( !MemError() ) { HLock( data ); // lock handle after increasing it's size BlockMoveData( buffer, *data + size, actualSize ); *((char *)(*data + size + actualSize)) = 0x00; // add zero byte HSetState( data, state ); } size += actualSize +1; } DisposePtr( buffer ); } } } break; case 'ECST': { id.id = n; id.signature = kEditFieldSignature; error = GetControlByID( window, &id, &control ); if( !error ) { // find out how much text has been typed Size bufferSize, actualSize; error = GetControlDataSize( control, kControlEditTextPart, kControlEditTextTextTag, &bufferSize ); if( !error ) { // allocate buffer and read data Ptr buffer = NewPtrClear( bufferSize ); error = GetControlData( control, kControlEditTextPart, kControlEditTextTextTag, bufferSize, buffer, &actualSize ); if( !error ) { // decide if I need a padding byte or not Boolean pad = (actualSize % 2 == 0)? true:false; // append correct data to handle SInt8 state = HGetState( data ); SetHandleSize( data, size + actualSize + (pad? 1:0) +1 ); if( !MemError() ) { HLock( data ); // lock handle after increasing it's size BlockMoveData( buffer, *data + size, actualSize ); *((char *)(*data + size + actualSize)) = 0x00; // add zero byte if( pad ) *((char *)(*data + size + actualSize +1)) = 0x00; // add pad byte HSetState( data, state ); } size += actualSize + (pad? 1:0) +1; } DisposePtr( buffer ); } } } break; case 'OCST': { id.id = n; id.signature = kEditFieldSignature; error = GetControlByID( window, &id, &control ); if( !error ) { // find out how much text has been typed Size bufferSize, actualSize; error = GetControlDataSize( control, kControlEditTextPart, kControlEditTextTextTag, &bufferSize ); if( !error ) { // allocate buffer and read data Ptr buffer = NewPtrClear( bufferSize ); error = GetControlData( control, kControlEditTextPart, kControlEditTextTextTag, bufferSize, buffer, &actualSize ); if( !error ) { // decide if I need a padding byte or not Boolean pad = (actualSize % 2 == 1)? true:false; // append correct data to handle SInt8 state = HGetState( data ); SetHandleSize( data, size + actualSize + (pad? 1:0) +1 ); if( !MemError() ) { HLock( data ); // lock handle after increasing it's size BlockMoveData( buffer, *data + size, actualSize ); *((char *)(*data + size + actualSize)) = 0x00; // add zero byte if( pad ) *((char *)(*data + size + actualSize +1)) = 0x00; // add pad byte HSetState( data, state ); } size += actualSize + (pad? 1:0) +1; } DisposePtr( buffer ); } } } break; case 'PSTR': { id.id = n; id.signature = kEditFieldSignature; error = GetControlByID( window, &id, &control ); if( !error ) { // find out how much text has been typed Size bufferSize, actualSize; error = GetControlDataSize( control, kControlEditTextPart, kControlEditTextTextTag, &bufferSize ); if( !error ) { // allocate buffer and read data Ptr buffer = NewPtrClear( bufferSize ); error = GetControlData( control, kControlEditTextPart, kControlEditTextTextTag, bufferSize, buffer, &actualSize ); if( !error ) { // append correct data to handle SInt8 state = HGetState( data ); SetHandleSize( data, size + actualSize +1 ); if( !MemError() ) { HLock( data ); // lock handle after increasing it's size *((char *)(*data + size)) = (char) actualSize; // add length byte BlockMoveData( buffer, *data + size +1, actualSize ); HSetState( data, state ); } size += actualSize +1; } DisposePtr( buffer ); } } } break; case 'ESTR': { id.id = n; id.signature = kEditFieldSignature; error = GetControlByID( window, &id, &control ); if( !error ) { // find out how much text has been typed Size bufferSize, actualSize; error = GetControlDataSize( control, kControlEditTextPart, kControlEditTextTextTag, &bufferSize ); if( !error ) { // allocate buffer and read data Ptr buffer = NewPtrClear( bufferSize ); error = GetControlData( control, kControlEditTextPart, kControlEditTextTextTag, bufferSize, buffer, &actualSize ); if( !error ) { // decide if I need a padding byte or not Boolean pad = (actualSize % 2 == 0)? true:false; // append correct data to handle SInt8 state = HGetState( data ); SetHandleSize( data, size + actualSize + (pad? 1:0) +1 ); if( !MemError() ) { HLock( data ); // lock handle after increasing it's size *((char *)(*data + size)) = (char) actualSize; // add length byte BlockMoveData( buffer, *data + size +1, actualSize ); if( pad ) *((char *)(*data + size + actualSize +1)) = 0x00; // add pad byte HSetState( data, state ); } size += actualSize + (pad? 1:0) +1; } DisposePtr( buffer ); } } } break; case 'OSTR': { id.id = n; id.signature = kEditFieldSignature; error = GetControlByID( window, &id, &control ); if( !error ) { // find out how much text has been typed Size bufferSize, actualSize; error = GetControlDataSize( control, kControlEditTextPart, kControlEditTextTextTag, &bufferSize ); if( !error ) { // allocate buffer and read data Ptr buffer = NewPtrClear( bufferSize ); error = GetControlData( control, kControlEditTextPart, kControlEditTextTextTag, bufferSize, buffer, &actualSize ); if( !error ) { // decide if I need a padding byte or not Boolean pad = (actualSize % 2 == 1)? true:false; // append correct data to handle SInt8 state = HGetState( data ); SetHandleSize( data, size + actualSize + (pad? 1:0) +1 ); if( !MemError() ) { HLock( data ); // lock handle after increasing it's size *((char *)(*data + size)) = (char) actualSize; // add length byte BlockMoveData( buffer, *data + size +1, actualSize ); if( pad ) *((char *)(*data + size + actualSize +1)) = 0x00; // add pad byte HSetState( data, state ); } size += actualSize + (pad? 1:0) +1; } DisposePtr( buffer ); } } } break; case 'WSTR': { id.id = n; id.signature = kEditFieldSignature; error = GetControlByID( window, &id, &control ); if( !error ) { // find out how much text has been typed Size bufferSize, actualSize; error = GetControlDataSize( control, kControlEditTextPart, kControlEditTextTextTag, &bufferSize ); if( !error ) { // allocate buffer and read data Ptr buffer = NewPtrClear( bufferSize ); error = GetControlData( control, kControlEditTextPart, kControlEditTextTextTag, bufferSize, buffer, &actualSize ); if( !error ) { // append correct data to handle SInt8 state = HGetState( data ); SetHandleSize( data, size + actualSize +2 ); if( !MemError() ) { HLock( data ); // lock handle after increasing it's size *((short *)(*data + size)) = (short) actualSize; // add length bytes BlockMoveData( buffer, *data + size +2, actualSize ); HSetState( data, state ); } size += actualSize +2; } DisposePtr( buffer ); } } } break; case 'LSTR': { id.id = n; id.signature = kEditFieldSignature; error = GetControlByID( window, &id, &control ); if( !error ) { // find out how much text has been typed Size bufferSize, actualSize; error = GetControlDataSize( control, kControlEditTextPart, kControlEditTextTextTag, &bufferSize ); if( !error ) { // allocate buffer and read data Ptr buffer = NewPtrClear( bufferSize ); error = GetControlData( control, kControlEditTextPart, kControlEditTextTextTag, bufferSize, buffer, &actualSize ); if( !error ) { // append correct data to handle SInt8 state = HGetState( data ); SetHandleSize( data, size + actualSize +4 ); if( !MemError() ) { HLock( data ); // lock handle after increasing it's size *((long *)(*data + size)) = (long) actualSize; // add length bytes BlockMoveData( buffer, *data + size +4, actualSize ); HSetState( data, state ); } size += actualSize +4; } DisposePtr( buffer ); } } } break; case 'HEXD': { id.id = n; id.signature = kEditFieldSignature; error = GetControlByID( window, &id, &control ); if( !error ) { // find out how much text has been typed Size bufferSize, actualSize; error = GetControlDataSize( control, kControlEditTextPart, kControlEditTextTextTag, &bufferSize ); if( !error ) { // allocate buffer and read data Ptr buffer = NewPtrClear( bufferSize ); error = GetControlData( control, kControlEditTextPart, kControlEditTextTextTag, bufferSize, buffer, &actualSize ); if( !error ) { // append correct data to handle SInt8 state = HGetState( data ); SetHandleSize( data, size + actualSize ); if( !MemError() ) { HLock( data ); // lock handle after increasing it's size BlockMoveData( buffer, *data + size, actualSize ); HSetState( data, state ); } size += actualSize; } DisposePtr( buffer ); } } } break; case 'RECT': { id.id = n; id.signature = kRectGroupSignature; error = GetControlByID( window, &id, &group ); if( !error ) { // read checkboxs Rect value; error = GetIndexedSubControl( group, 2, &control ); if( !error ) { // find out how much text has been typed Size bufferSize, actualSize; error = GetControlDataSize( control, kControlEditTextPart, kControlEditTextTextTag, &bufferSize ); if( !error ) { // allocate buffer and read data Ptr buffer = NewPtrClear( bufferSize ); error = GetControlData( control, kControlEditTextPart, kControlEditTextTextTag, bufferSize, buffer, &actualSize ); if( !error ) { // convert buffer into desired format SInt32 number; Str255 string; string[0] = actualSize; BlockMoveData( buffer, &string[1], actualSize ); StringToNum( string, &number ); value.top = (short) number; } DisposePtr( buffer ); } } else value.top = 0; error = GetIndexedSubControl( group, 4, &control ); if( !error ) { // find out how much text has been typed Size bufferSize, actualSize; error = GetControlDataSize( control, kControlEditTextPart, kControlEditTextTextTag, &bufferSize ); if( !error ) { // allocate buffer and read data Ptr buffer = NewPtrClear( bufferSize ); error = GetControlData( control, kControlEditTextPart, kControlEditTextTextTag, bufferSize, buffer, &actualSize ); if( !error ) { // convert buffer into desired format SInt32 number; Str255 string; string[0] = actualSize; BlockMoveData( buffer, &string[1], actualSize ); StringToNum( string, &number ); value.left = (short) number; } DisposePtr( buffer ); } } else value.left = 0; error = GetIndexedSubControl( group, 6, &control ); if( !error ) { // find out how much text has been typed Size bufferSize, actualSize; error = GetControlDataSize( control, kControlEditTextPart, kControlEditTextTextTag, &bufferSize ); if( !error ) { // allocate buffer and read data Ptr buffer = NewPtrClear( bufferSize ); error = GetControlData( control, kControlEditTextPart, kControlEditTextTextTag, bufferSize, buffer, &actualSize ); if( !error ) { // convert buffer into desired format SInt32 number; Str255 string; string[0] = actualSize; BlockMoveData( buffer, &string[1], actualSize ); StringToNum( string, &number ); value.bottom = (short) number; } DisposePtr( buffer ); } } else value.bottom = 0; error = GetIndexedSubControl( group, 8, &control ); if( !error ) { // find out how much text has been typed Size bufferSize, actualSize; error = GetControlDataSize( control, kControlEditTextPart, kControlEditTextTextTag, &bufferSize ); if( !error ) { // allocate buffer and read data Ptr buffer = NewPtrClear( bufferSize ); error = GetControlData( control, kControlEditTextPart, kControlEditTextTextTag, bufferSize, buffer, &actualSize ); if( !error ) { // convert buffer into desired format SInt32 number; Str255 string; string[0] = actualSize; BlockMoveData( buffer, &string[1], actualSize ); StringToNum( string, &number ); value.right = (short) number; } DisposePtr( buffer ); } } else value.right = 0; // append correct data to handle SInt8 state = HGetState( data ); SetHandleSize( data, size +8 ); if( !MemError() ) { HLock( data ); // lock handle after increasing it's size BlockMoveData( &value, *data + size, 8 ); HSetState( data, state ); } size += 8; } } break; case 'ZCNT': case 'OCNT': { id.id = n; id.signature = kListCountSignature; error = GetControlByID( window, &id, &control ); if( !error ) { // find out how much text has been typed Size bufferSize, actualSize; error = GetControlDataSize( control, kControlLabelPart, kControlStaticTextTextTag, &bufferSize ); if( !error ) { // allocate buffer and read data Ptr buffer = NewPtrClear( bufferSize ); error = GetControlData( control, kControlLabelPart, kControlStaticTextTextTag, bufferSize, buffer, &actualSize ); if( !error ) { // convert buffer into desired format SInt16 value; SInt32 number; Str255 string; string[0] = actualSize; BlockMoveData( buffer, &string[1], actualSize ); StringToNum( string, &number ); value = (short) number; // set recursion total (number of times to repeat) recursionTotal = value; // actually, this value is ignored :) -- I count occourences of LSTE instead // append correct data to handle SInt8 state = HGetState( data ); SetHandleSize( data, size +2 ); if( !MemError() ) { HLock( data ); // lock handle after increasing it's size BlockMoveData( &value, *data + size, 2 ); HSetState( data, state ); } size += 2; } DisposePtr( buffer ); } } } break; case 'LSTB': // repeats to end of resource { recursionTotal = -1; recursionIndex = m; recursionOrigin = current; } break; case 'LSTC': // repeats recursionTotal times { recursionIndex = m; recursionOrigin = current; } break; case 'LSTZ': // repeats until 0x00 is encountered as first byte of next block { recursionTotal = -2; recursionIndex = m; recursionOrigin = current; } break; case 'LSTE': { recursionCount++; /* if( recursionTotal == -2 ) { UInt8 content = 0x00; if( size > position ) content = *((unsigned char*)(*data + position)); if( content != 0x00 ) { // set current parser back to where recursion began from m = recursionIndex; current = recursionOrigin; } } else if( recursionTotal == -1 ) { if( size > position ) { // set current parser back to where recursion began from m = recursionIndex; current = recursionOrigin; } } else if( recursionCount < recursionTotal ) { // set current parser back to where recursion began from m = recursionIndex; current = recursionOrigin; } */ } break; default: break; } // advance current element current = current->next; } // save any changes if( !error ) { Handle original = Host_GetResourceData( resource ); Host_DebugError( "\ptesting if handles matchÉ", 0 ); if( !HandlesMatch( original, data ) ) // only save if data changed { Host_DebugError( "\pthey don't, saving changesÉ", 0 ); SInt8 state = HGetState( original ); HLock( original ); SetHandleSize( original, size ); BlockMoveData( *data, *original, size ); HSetState( original, state ); Host_SetResourceDirty( resource, true ); } else Host_DebugError( "\pthey do match, don't save changes", 0 ); Host_ReleaseResourceData( resource ); } else Host_DebugError( "\pan error occoured somewhere, don't save any changes", error ); // clean up and leave DisposeHandle( data ); return error; } /*** HANDLES MATCH ***/ Boolean HandlesMatch( const Handle one, const Handle two ) { Size sizeOne = GetHandleSize( one ); Size sizeTwo = GetHandleSize( two ); if( sizeOne != sizeTwo ) return false; else { SInt8 stateOne = HGetState( one ); SInt8 stateTwo = HGetState( two ); HLock( one ); HLock( two ); char *a = *one; char *b = *two; for( unsigned short i = 0; i < sizeOne; i++ ) if( *a++ != *b++ ) return false; HSetState( one, stateOne ); HSetState( two, stateTwo ); return true; } } \ No newline at end of file diff --git a/Template Editor/Classes/TemplateWindow.h b/Template Editor/Classes/TemplateWindow.h new file mode 100644 index 0000000..ce1b04e --- /dev/null +++ b/Template Editor/Classes/TemplateWindow.h @@ -0,0 +1,48 @@ +#include "Template Editor.h" + +typedef class TemplateWindow TemplateWindow, *TemplateWindowPtr; +typedef class Element Element, *ElementPtr; + +/*** CONSTANTS ***/ +const UInt32 kHeaderSignature = FOUR_CHAR_CODE('head'); +const UInt32 kLabelSignature = FOUR_CHAR_CODE('labl'); +const UInt32 kBooleanGroupSignature = FOUR_CHAR_CODE('bool'); +const UInt32 kRectGroupSignature = FOUR_CHAR_CODE('rect'); +const UInt32 kEditFieldSignature = FOUR_CHAR_CODE('edit'); +const UInt32 kHexDumpSignature = FOUR_CHAR_CODE('hdmp'); +const UInt32 kListCountSignature = FOUR_CHAR_CODE('list'); +const UInt32 kScrollbarSignature = FOUR_CHAR_CODE('scrl'); + +/*** TEMPLATE WINDOW CLASS ***/ +class TemplateWindow +{ + WindowRef window; + Handle tmpl; + UInt32 elementCount; + ElementPtr elements; + Rect bounds; // the bounds of the previous control + +public: + TemplateWindow( WindowRef newWindow ); + ~TemplateWindow( void ); + OSStatus UseTemplate( Handle newTmpl ); + OSStatus ParseData( Handle data ); +private: + OSStatus ParseTemplate( void ); + OSStatus CreateControls( void ); + OSStatus ReadControls( void ); +}; + +/*** TEMPLATE ELEMENT CLASS ***/ +class Element +{ + Str255 label; + UInt32 type; +// need ControlHandle here (only one, can embed within) + ElementPtr next; + + Element( void ); + friend class TemplateWindow; +}; + +Boolean HandlesMatch( const Handle one, const Handle two ); \ No newline at end of file diff --git a/Template Editor/Template Editor.h b/Template Editor/Template Editor.h new file mode 100644 index 0000000..bb23602 --- /dev/null +++ b/Template Editor/Template Editor.h @@ -0,0 +1 @@ +#if !TARGET_API_MAC_OS8 #include #endif #ifndef _ResKnife_Plug_ #define _ResKnife_Plug_ 1 #include "HostCallbacks.h" #endif #ifndef _ResKnife_TemplateEditor_ #define _ResKnife_TemplateEditor_ #include "Generic.h" // abbreviations #define Use_Nibs 0 #define Use_GWorlds 1 /* Global Variables */ struct globals { // application Str255 fragName; Str255 prefsName; // system info SInt32 systemVersion; Boolean dragAvailable; Boolean translucentDrag; Boolean navAvailable; Boolean useAppearance; // colours RGBColor white; // 0xFFFF, 65535 RGBColor bgColour; // 0xEEEE, 61166 RGBColor black; // 0x0000, 0 }; /* Preferences */ struct prefs { UInt32 version; // == kTemplateEditorCurrentVersion, when saved to disk allows older prefs to be read in UInt8 GWorldDepth; }; /*** CONSTANTS ***/ const UInt32 kTemplateEditorCurrentVersion = 0x00030003; const UInt16 kHeaderHeight = 20; const UInt16 kScrollBarWidth = 16; const UInt16 kMinimumWindowWidth = 384; const UInt16 kDefaultWindowWidth = kMinimumWindowWidth; const UInt16 kMinimumWindowHeight = 256 + kHeaderHeight; const UInt16 kDefaultWindowHeight = kMinimumWindowHeight; // MacOS versions const SInt32 kMacOSSevenPointOne = 0x00000710; const SInt32 kMacOSSevenPointFivePointFive = 0x00000755; const SInt32 kMacOSEight = 0x00000800; const SInt32 kMacOSEightPointFive = 0x00000850; const SInt32 kMacOSEightPointSix = 0x00000860; const SInt32 kMacOSNine = 0x00000900; const SInt32 kMacOSNinePointOne = 0x00000910; const SInt32 kMacOSTen = 0x00001000; const SInt32 kMacOS71 = kMacOSSevenPointOne; const SInt32 kMacOS755 = kMacOSSevenPointFivePointFive; const SInt32 kMacOS8 = kMacOSEight; const SInt32 kMacOS85 = kMacOSEightPointFive; const SInt32 kMacOS86 = kMacOSEightPointSix; const SInt32 kMacOS9 = kMacOSNine; const SInt32 kMacOS91 = kMacOSNinePointOne; const SInt32 kMacOSX = kMacOSTen; /* RESOURCES */ enum // menus { kEditorMenu = 128 }; enum // windows { kFileWindow7 = 128, kFileWindow8 = 129 }; enum // controls { kSystem7ScrollBarControl = 128, kAppearanceScrollBarControl = 129, kNormalHeaderControl = 130 }; #endif \ No newline at end of file diff --git a/Template Editor/Template Editor.r b/Template Editor/Template Editor.r new file mode 100644 index 0000000..db0231d --- /dev/null +++ b/Template Editor/Template Editor.r @@ -0,0 +1,30 @@ +#ifdef __MWERKS__ + #include +#else + #include +#endif + +/*** FILE MENU ***/ +resource 'MENU' (2001) +{ + 2001, + textMenuProc, + 0xFFFFFFFF, + enabled, + "Rectangle Style", + { + "Bottom Right", noIcon, noKey, noMark, plain, + "Width & Height", noIcon, noKey, noMark, plain, + } +}; + +resource 'xmnu' (2001, purgeable) +{ + versionZero + { + { + dataItem { 'rela', kMenuNoModifiers, currScript, 0, 0, noHierID, sysFont, naturalGlyph }, + dataItem { 'abso', kMenuNoModifiers, currScript, 0, 0, noHierID, sysFont, naturalGlyph } + } + }; +}; \ No newline at end of file