Create classic Mac OS GUI program

The GUI program's project is not checked in. It was compiled and tested
with CodeWarrior Pro 4.

The GUI program allows users to create new synchronization projects,
select folders to synchronize, and save and open projects. The code is
probably broken and buggy, and probably dereferences NULL pointers here
and there, but the basic shell is there.
This commit is contained in:
Dietrich Epp 2022-04-10 04:09:17 -04:00
parent dd96385d7c
commit 4d4ee214b3
19 changed files with 1955 additions and 0 deletions

105
macos/choose_directory.c Normal file
View File

@ -0,0 +1,105 @@
#include "choose_directory.h"
#include "error.h"
#include "resources.h"
#include "strutil.h"
#include <string.h>
typedef enum {
kCDNone,
kCDChild,
kCDCurrent,
} ChooseDirStatus;
struct ChooseDirReply {
ChooseDirStatus status;
FSSpec *directory;
};
// FIXME: Don't use pascal on PowerMac.
static pascal Boolean ChooseDirectoryFileFilter(CInfoPBPtr pb,
void *yourDataPtr)
{
(void)yourDataPtr;
return (pb->hFileInfo.ioFlAttrib & ioDirMask) == 0;
}
enum {
kCDItemChoose = 10,
kCDItemChooseCurrent = 11,
};
static void SelectCurrentDirectory(struct ChooseDirReply *reply)
{
CInfoPBRec ci;
OSStatus err;
Str63 name;
FSSpec *directory;
memset(&ci, 0, sizeof(ci));
ci.dirInfo.ioNamePtr = name;
ci.dirInfo.ioVRefNum = -LMGetSFSaveDisk();
ci.dirInfo.ioFDirIndex = -1;
ci.dirInfo.ioDrDirID = LMGetCurDirStore();
err = PBGetCatInfoSync(&ci);
if (err != 0) {
ExitErrorOS(kErrUnknown, err);
}
directory = reply->directory;
directory->vRefNum = ci.dirInfo.ioVRefNum;
directory->parID = ci.dirInfo.ioDrParID;
StrCopy(directory->name, sizeof(directory->name), name);
reply->status = kCDCurrent;
}
static pascal short ChooseDirectoryDialogHook(short item, DialogRef theDialog,
void *yourDataPtr)
{
struct ChooseDirReply *reply;
if (GetWRefCon((WindowRef)theDialog) != sfMainDialogRefCon) {
return item;
}
reply = yourDataPtr;
switch (item) {
case kCDItemChoose:
reply->status = kCDChild;
return sfItemCancelButton;
case kCDItemChooseCurrent:
SelectCurrentDirectory(yourDataPtr);
return sfItemCancelButton;
default:
return item;
}
}
Boolean ChooseDirectory(FSSpec *directory)
{
struct ChooseDirReply cdreply;
FileFilterYDUPP fileFilter;
DlgHookYDUPP dlgHook;
StandardFileReply sfreply;
Point where;
cdreply.status = kCDNone;
cdreply.directory = directory;
fileFilter = NewFileFilterYDProc(ChooseDirectoryFileFilter);
ASSERT(fileFilter != NULL);
dlgHook = NewDlgHookYDProc(ChooseDirectoryDialogHook);
ASSERT(dlgHook != NULL);
where.v = -1;
where.h = -1;
CustomGetFile(fileFilter, -1, NULL, &sfreply, rDLOG_OpenFolder, where,
dlgHook, NULL /* filterProc */, 0, NULL, &cdreply);
// DisposeFileFilterYDUPP(fileFilter);
// DisposeDlgHookYDUPP(dlgHook);
switch (cdreply.status) {
case kCDChild:
BlockMoveData(&sfreply.sfFile, directory, sizeof(FSSpec));
return 1;
case kCDCurrent:
return 1;
default:
return 0;
}
}

8
macos/choose_directory.h Normal file
View File

@ -0,0 +1,8 @@
#ifndef MACOS_CHOOSE_DIRECTORY_H
#define MACOS_CHOOSE_DIRECTORY_H
// Present a dialog box to choose a directory. Returns true and initializes
// directory if a directory is selected.
Boolean ChooseDirectory(FSSpec *directory);
#endif

1
macos/derez.sh Normal file
View File

@ -0,0 +1 @@
DeRez icons.rsrc | iconv -f MACINTOSH -t UTF-8 | sed 's# */.*##' > icons.r

72
macos/error.c Normal file
View File

@ -0,0 +1,72 @@
#include "error.h"
#include "main.h"
#include "resources.h"
#include "strutil.h"
#include <Dialogs.h>
#include <TextUtils.h>
void ExitError(ErrorCode errCode)
{
Str255 message;
GetIndString(message, rSTRS_Errors, errCode);
if (message[0] == 0) {
GetIndString(message, rSTRS_Errors, kErrUnknown);
}
ParamText(message, NULL, NULL, NULL);
Alert(rAlrtError, NULL);
QuitApp();
}
void ExitErrorOS(ErrorCode errCode, short osErr)
{
Str255 message;
GetIndString(message, rSTRS_Errors, errCode);
if (message[0] == 0) {
GetIndString(message, rSTRS_Errors, kErrUnknown);
}
StrAppendFormat(message, "\p\rError code: %d", osErr);
ParamText(message, NULL, NULL, NULL);
Alert(rAlrtError, NULL);
QuitApp();
}
void ExitMemError(void)
{
ExitErrorOS(kErrOutOfMemory, MemError());
}
void ExitAssert(const unsigned char *file, int line,
const unsigned char *message)
{
Str255 dmessage;
GetIndString(dmessage, rSTRS_Errors, kErrInternal);
StrAppendFormat(dmessage, "\p\rError at: %S:%d", file, line);
if (message != NULL) {
StrAppendFormat(dmessage, "\p: %S", message);
}
ParamText(dmessage, NULL, NULL, NULL);
Alert(rAlrtError, NULL);
QuitApp();
}
void ShowError(const struct ErrorParams *p)
{
Str255 message;
GetIndString(message, rSTRS_Errors, p->err);
if (message[0] == 0) {
GetIndString(message, rSTRS_Errors, kErrUnknown);
} else if (p->strParam != NULL) {
StrSubstitute(message, p->strParam);
}
if (p->osErr != NULL) {
StrAppendFormat(message, "\p\rError code: %d", p->osErr);
}
ParamText(message, NULL, NULL, NULL);
Alert(rAlrtError, NULL);
}

48
macos/error.h Normal file
View File

@ -0,0 +1,48 @@
// error.h - Error handling.
#ifndef MACOS_ERROR_H
#define MACOS_ERROR_H
// Error codes, corresponding to messages in a STR# resource.
typedef enum {
kErrUnknown = 1,
kErrInternal,
kErrOutOfMemory,
kErrCouldNotSaveProject,
} ErrorCode;
struct Error {
ErrorCode code;
short osErr;
};
// Show an error dialog with the given error message, then quit the program.
void ExitError(ErrorCode errCode);
// Show an error dialog with the given error message and an OS error code, then
// quit the program.
void ExitErrorOS(ErrorCode errCode, short osErr);
// Show an out of memory error and quit the program.
void ExitMemError(void);
// Show an assertion error and quit the program.
void ExitAssert(const unsigned char *file, int line,
const unsigned char *message);
#define EXIT_ASSERT(str) ExitAssert("\p" __FILE__, __LINE__, str)
#define ASSERT(p) \
do { \
if (!(p)) \
ExitAssert("\p" __FILE__, __LINE__, "\p" #p); \
} while (0)
struct ErrorParams {
ErrorCode err;
OSErr osErr;
const unsigned char *strParam;
};
void ShowError(const struct ErrorParams *p);
#endif

316
macos/icons.r Normal file
View File

@ -0,0 +1,316 @@
data 'icl8' (128) {
$"0000 0000 0000 0000 0000 0000 0000 00FF"
$"0000 0000 0000 00FF 0000 0000 0000 0000"
$"0000 0000 0000 0000 0000 0000 0000 FF9F"
$"FF00 0000 0000 FFC0 FF00 0000 0000 0000"
$"0000 0000 0000 0000 0000 0000 00FF 9F92"
$"9FFF 0000 00FF C0C0 C0FF 0000 0000 0000"
$"0000 0000 0000 0000 0000 0000 FF9F 929F"
$"929F FF00 FFC0 C0C0 C0C0 FF00 0000 0000"
$"0000 0000 0000 00FF FFFF FFFF FFFF FFFF"
$"9F92 9FFF C0C0 C0C0 C0C0 C0FF 0000 0000"
$"0000 0000 0000 FFFF F5F5 F5F5 F5F5 F5FF"
$"929F FFFF FFFF C0C0 C0FF FFFF FF00 0000"
$"0000 0000 00FF 2BFF F5F5 F5FF FFFF FFFF"
$"FFFF FFFF 9FFF C0C0 C0FF 0000 0000 0000"
$"0000 0000 FFFF FFFF F5F5 FFFF F5F5 F5F5"
$"F5F5 F5FF 92FF C0C0 C0FF 0000 0000 0000"
$"0000 0000 FFF5 F5F5 F5FF 2BFF F5F5 F5F5"
$"F5F5 F5FF 9FFF C0C0 C0FF 0000 0000 0000"
$"0000 0000 FFF5 F5F5 FFFF FFFF F5F5 F5F5"
$"F5F5 F5FF 92FF C0C0 C0FF 0000 0000 0000"
$"0000 0000 FFF5 F5F5 FFF5 F5F5 F5F5 F5F5"
$"F5F5 F5FF 9FFF C0C0 C0FF 0000 0000 0000"
$"0000 0000 FFF5 F5F5 FFF5 F5F5 F5F5 FFFF"
$"FFFF F5FF 92FF C0C0 C0FF FF00 0000 0000"
$"0000 00FF FFF5 F5F5 FFF5 F5F5 F5FF 2A2A"
$"2A2A FFFF 9FFF C0C0 C0FF 9FFF 0000 0000"
$"0000 FF9F FFF5 F5F5 FFF5 F5F5 FF2A 2A2A"
$"2A2A 2AFF FFFF FFFF FFFF FF9F FF00 0000"
$"00FF 9F92 FFF5 F5F5 FFF5 F5F5 FF00 2A00"
$"2A00 2A00 2A00 2A00 2A00 54FF 9FFF 0000"
$"FF9F 929F FFF5 F5F5 FFF5 F5F5 FF2A 002A"
$"002A 002A 002A 002A 002A 54FF 929F FF00"
$"00FF 9F92 FFF5 F5F5 FFF5 F5F5 FF00 2A00"
$"2A00 2A00 2A00 2A00 2A00 54FF 9F92 9FFF"
$"0000 FF9F FFF5 F5F5 FFF5 F5F5 FF2A 002A"
$"002A 002A 002A 002A 002A 54FF 929F FF00"
$"0000 00FF FFF5 F5F5 FFF5 F5F5 FF00 2A00"
$"2A00 2A00 2A00 2A00 2A00 54FF 9FFF 0000"
$"0000 0000 FFFF FFFF FFF5 F5F5 FF2A 002A"
$"002A 002A 002A 002A 002A 54FF FF00 0000"
$"0000 0000 00FF FF69 FFF5 F5F5 FF00 2A00"
$"2A00 2A00 2A00 2A00 2A00 54FF 0000 0000"
$"0000 0000 0000 FF69 FFFF FFFF FF2A 002A"
$"002A 002A 002A 002A 002A 54FF 0000 0000"
$"0000 0000 0000 FF69 6969 FF92 FF00 2A00"
$"2A00 2A00 2A00 2A00 2A00 54FF 0000 0000"
$"0000 0000 0000 FF69 6969 FF9F FFFF FFFF"
$"FFFF FFFF FFFF FFFF FFFF FFFF 0000 0000"
$"0000 0000 0000 FF69 6969 FF92 9F92 9F92"
$"9F92 9F92 9F92 9FFF 0000 0000 0000 0000"
$"0000 0000 0000 FF69 6969 FF9F 929F 929F"
$"929F 929F 929F FF00 0000 0000 0000 0000"
$"0000 00FF FFFF FF69 6969 FFFF FFFF 9F92"
$"9F92 9F92 9FFF 0000 0000 0000 0000 0000"
$"0000 0000 FF69 6969 6969 6969 FF9F 929F"
$"929F 929F FF00 0000 0000 0000 0000 0000"
$"0000 0000 00FF 6969 6969 69FF 00FF 9F92"
$"9F92 9FFF 0000 0000 0000 0000 0000 0000"
$"0000 0000 0000 FF69 6969 FF00 0000 FF9F"
$"929F FF00 0000 0000 0000 0000 0000 0000"
$"0000 0000 0000 00FF 69FF 0000 0000 00FF"
$"9FFF 0000 0000 0000 0000 0000 0000 0000"
$"0000 0000 0000 0000 FF00 0000 0000 0000"
$"FF00 0000 0000 0000 0000 0000 0000 0000"
};
data 'icl8' (129) {
$"0000 00FF FFFF FFFF FFFF FFFF FFFF FFFF"
$"FFFF FFFF FFFF 0000 0000 0000 0000 0000"
$"0000 00FF F5F5 F5F5 F5F5 F5F5 F5F5 F5F5"
$"F5F5 F5F5 F5FF FF00 0000 0000 0000 0000"
$"0000 00FF F5F5 F5F5 F5F5 F5F5 F5F5 F5F5"
$"F5F5 F5F5 F5FF 2BFF 0000 0000 0000 0000"
$"0000 00FF F5F5 F5F5 F5F5 F5FF F5F5 F5F5"
$"F5F5 F5F5 F5FF 2B2B FF00 0000 0000 0000"
$"0000 00FF F5F5 F5F5 F5F5 FFC0 FFF5 F5F5"
$"F5F5 F5F5 F5FF 2B2B 2BFF 0000 0000 0000"
$"0000 00FF F5F5 F5F5 F5FF C0C0 C0FF F5F5"
$"F5F5 F5F5 F5FF 2B2B 2B2B FF00 0000 0000"
$"0000 00FF F5F5 F5F5 FFC0 C0C0 C0C0 FFF5"
$"F5F5 F5F5 F5FF FFFF FFFF FFFF 0000 0000"
$"0000 00FF F5F5 F5FF C0C0 C0C0 C0C0 C0FF"
$"F5F5 F5F5 F5F5 F5F5 F5F5 F5FF 0000 0000"
$"0000 00FF F5F5 FFFF FFFF C0C0 C0FF FFFF"
$"FFF5 F5F5 F5F5 F5F5 F5F5 F5FF 0000 0000"
$"0000 00FF F5F5 F5F5 F5FF C0C0 C0FF F5F5"
$"F5F5 F5F5 F5F5 F5F5 F5F5 F5FF 0000 0000"
$"0000 00FF F5F5 F5F5 F5FF C0C0 C0FF F5F5"
$"F5F5 F5F5 F5F5 F5F5 F5F5 F5FF 0000 0000"
$"0000 00FF F5F5 F5F5 F5FF C0C0 C0FF F5F5"
$"F5FF FFFF FFFF F5F5 F5F5 F5FF 0000 0000"
$"0000 00FF F5F5 F5F5 F5FF C0C0 C0FF F5F5"
$"F5FF 6969 69FF F5F5 F5F5 F5FF 0000 0000"
$"0000 00FF F5F5 F5F5 F5FF C0C0 C0FF F5F5"
$"F5FF 6969 69FF F5F5 F5F5 F5FF 0000 0000"
$"0000 00FF F5F5 F5F5 F5FF C0C0 C0FF F5F5"
$"F5FF 6969 69FF F5F5 F5F5 F5FF 0000 0000"
$"0000 00FF F5F5 F5F5 F5FF C0C0 C0FF F5F5"
$"F5FF 6969 69FF F5F5 F5F5 F5FF 0000 0000"
$"0000 00FF F5F5 F5F5 F5FF C0C0 C0FF F5F5"
$"F5FF 6969 69FF F5F5 F5F5 F5FF 0000 0000"
$"0000 00FF F5F5 F5F5 F5FF C0C0 C0FF F5F5"
$"F5FF 6969 69FF F5F5 F5F5 F5FF 0000 0000"
$"0000 00FF F5F5 F5F5 F5FF C0C0 C0FF F5F5"
$"F5FF 6969 69FF F5F5 F5F5 F5FF 0000 0000"
$"0000 00FF F5F5 F5F5 F5FF C0C0 C0FF F5F5"
$"F5FF 6969 69FF F5F5 F5F5 F5FF 0000 0000"
$"0000 00FF F5F5 F5F5 F5FF FFFF FFFF F5F5"
$"F5FF 6969 69FF F5F5 F5F5 F5FF 0000 0000"
$"0000 00FF F5F5 F5F5 F5F5 F5F5 F5F5 F5F5"
$"F5FF 6969 69FF F5F5 F5F5 F5FF 0000 0000"
$"0000 00FF F5F5 F5F5 F5F5 F5F5 F5F5 F5F5"
$"F5FF 6969 69FF F5F5 F5F5 F5FF 0000 0000"
$"0000 00FF F5F5 F5F5 F5F5 F5F5 F5F5 FFFF"
$"FFFF 6969 69FF FFFF FFF5 F5FF 0000 0000"
$"0000 00FF F5F5 F5F5 F5F5 F5F5 F5F5 F5FF"
$"6969 6969 6969 69FF F5F5 F5FF 0000 0000"
$"0000 00FF F5F5 F5F5 F5F5 F5F5 F5F5 F5F5"
$"FF69 6969 6969 FFF5 F5F5 F5FF 0000 0000"
$"0000 00FF F5F5 F5F5 F5F5 F5F5 F5F5 F5F5"
$"F5FF 6969 69FF F5F5 F5F5 F5FF 0000 0000"
$"0000 00FF F5F5 F5F5 F5F5 F5F5 F5F5 F5F5"
$"F5F5 FF69 FFF5 F5F5 F5F5 F5FF 0000 0000"
$"0000 00FF F5F5 F5F5 F5F5 F5F5 F5F5 F5F5"
$"F5F5 F5FF F5F5 F5F5 F5F5 F5FF 0000 0000"
$"0000 00FF F5F5 F5F5 F5F5 F5F5 F5F5 F5F5"
$"F5F5 F5F5 F5F5 F5F5 F5F5 F5FF 0000 0000"
$"0000 00FF F5F5 F5F5 F5F5 F5F5 F5F5 F5F5"
$"F5F5 F5F5 F5F5 F5F5 F5F5 F5FF 0000 0000"
$"0000 00FF FFFF FFFF FFFF FFFF FFFF FFFF"
$"FFFF FFFF FFFF FFFF FFFF FFFF 0000 0000"
};
data 'ICN#' (128) {
$"0001 0100 0002 8280 0004 4440 0008 2820"
$"01FF 1010 0301 3C78 051F F440 0F30 1440"
$"0850 1440 08F0 1440 0880 1440 0883 D460"
$"1884 3450 288F FFE8 4888 0014 8888 0012"
$"4888 0011 2888 0012 1888 0014 0F88 0018"
$"0688 0010 02F8 0010 0228 0010 022F FFF0"
$"0220 0100 0220 0200 1E3C 0400 0808 0800"
$"0414 1000 0222 2000 0141 4000 0080 8000"
$"0001 0100 0003 8380 0007 C7C0 000F EFE0"
$"01FF FFF0 03FF FFF8 07FF FFC0 0FFF FFC0"
$"0FFF FFC0 0FFF FFC0 0FFF FFC0 0FFF FFE0"
$"1FFF FFF0 3FFF FFF8 7FFF FFFC FFFF FFFE"
$"7FFF FFFF 3FFF FFFE 1FFF FFFC 0FFF FFF8"
$"07FF FFF0 03FF FFF0 03FF FFF0 03FF FFF0"
$"03FF FF00 03FF FE00 1FFF FC00 0FFF F800"
$"07F7 F000 03E3 E000 01C1 C000 0080 8000"
};
data 'ICN#' (129) {
$"1FFF FC00 1000 0600 1000 0500 1010 0480"
$"1028 0440 1044 0420 1082 07F0 1101 0010"
$"13C7 8010 1044 0010 1044 0010 1044 7C10"
$"1044 4410 1044 4410 1044 4410 1044 4410"
$"1044 4410 1044 4410 1044 4410 1044 4410"
$"107C 4410 1000 4410 1000 4410 1003 C790"
$"1001 0110 1000 8210 1000 4410 1000 2810"
$"1000 1010 1000 0010 1000 0010 1FFF FFF0"
$"1FFF FC00 1FFF FE00 1FFF FF00 1FFF FF80"
$"1FFF FFC0 1FFF FFE0 1FFF FFF0 1FFF FFF0"
$"1FFF FFF0 1FFF FFF0 1FFF FFF0 1FFF FFF0"
$"1FFF FFF0 1FFF FFF0 1FFF FFF0 1FFF FFF0"
$"1FFF FFF0 1FFF FFF0 1FFF FFF0 1FFF FFF0"
$"1FFF FFF0 1FFF FFF0 1FFF FFF0 1FFF FFF0"
$"1FFF FFF0 1FFF FFF0 1FFF FFF0 1FFF FFF0"
$"1FFF FFF0 1FFF FFF0 1FFF FFF0 1FFF FFF0"
};
data 'icl4' (128) {
$"0000 0000 0000 000F 0000 000F 0000 0000"
$"0000 0000 0000 00F8 F000 00F7 F000 0000"
$"0000 0000 0000 0F88 8F00 0F77 7F00 0000"
$"0000 0000 0000 F888 88F0 F777 77F0 0000"
$"0000 000F FFFF FFFF 888F 7777 777F 0000"
$"0000 00FF 0C0C 0C0F 88FF FF77 7FFF F000"
$"0000 0FCF C0CF FFFF FFFF 8F77 7F00 0000"
$"0000 FFFF 0CFF 0C0C 0C0F 8F77 7F00 0000"
$"0000 F0C0 CFCF C0C0 C0CF 8F77 7F00 0000"
$"0000 FC0C FFFF 0C0C 0C0F 8F77 7F00 0000"
$"0000 F0C0 F0C0 C0C0 C0CF 8F77 7F00 0000"
$"0000 FC0C FC0C 0CFF FF0F 8F77 7FF0 0000"
$"000F F0C0 F0C0 CFCC CCFF 8F77 7F8F 0000"
$"00F8 FC0C FC0C FCCC CCCF FFFF FFF8 F000"
$"0F88 F0C0 F0C0 F0C0 C0C0 C0C0 C0CF 8F00"
$"F888 FC0C FC0C FC0C 0C0C 0C0C 0CCF 88F0"
$"0F88 F0C0 F0C0 F0C0 C0C0 C0C0 C0CF 888F"
$"00F8 FC0C FC0C FC0C 0C0C 0C0C 0CCF 88F0"
$"000F F0C0 F0C0 F0C0 C0C0 C0C0 C0CF 8F00"
$"0000 FFFF FC0C FC0C 0C0C 0C0C 0CCF F000"
$"0000 0FF4 F0C0 F0C0 C0C0 C0C0 C0CF 0000"
$"0000 00F4 FFFF FC0C 0C0C 0C0C 0CCF 0000"
$"0000 00F4 44F8 F0C0 C0C0 C0C0 C0CF 0000"
$"0000 00F4 44F8 FFFF FFFF FFFF FFFF 0000"
$"0000 00F4 44F8 8888 8888 888F 0000 0000"
$"0000 00F4 44F8 8888 8888 88F0 0000 0000"
$"000F FFF4 44FF FF88 8888 8F00 0000 0000"
$"0000 F444 4444 F888 8888 F000 0000 0000"
$"0000 0F44 444F 0F88 888F 0000 0000 0000"
$"0000 00F4 44F0 00F8 88F0 0000 0000 0000"
$"0000 000F 4F00 000F 8F00 0000 0000 0000"
$"0000 0000 F000 0000 F000 0000 0000 0000"
};
data 'icl4' (129) {
$"000F FFFF FFFF FFFF FFFF FF00 0000 0000"
$"000F 0C0C 0C0C 0C0C 0C0C 0FF0 0000 0000"
$"000F C0C0 C0C0 C0C0 C0C0 CFCF 0000 0000"
$"000F 0C0C 0C0F 0C0C 0C0C 0FCC F000 0000"
$"000F C0C0 C0F7 F0C0 C0C0 CFCC CF00 0000"
$"000F 0C0C 0F77 7F0C 0C0C 0FCC CCF0 0000"
$"000F C0C0 F777 77F0 C0C0 CFFF FFFF 0000"
$"000F 0C0F 7777 777F 0C0C 0C0C 0C0F 0000"
$"000F C0FF FF77 7FFF F0C0 C0C0 C0CF 0000"
$"000F 0C0C 0F77 7F0C 0C0C 0C0C 0C0F 0000"
$"000F C0C0 CF77 7FC0 C0C0 C0C0 C0CF 0000"
$"000F 0C0C 0F77 7F0C 0FFF FF0C 0C0F 0000"
$"000F C0C0 CF77 7FC0 CF44 4FC0 C0CF 0000"
$"000F 0C0C 0F77 7F0C 0F44 4F0C 0C0F 0000"
$"000F C0C0 CF77 7FC0 CF44 4FC0 C0CF 0000"
$"000F 0C0C 0F77 7F0C 0F44 4F0C 0C0F 0000"
$"000F C0C0 CF77 7FC0 CF44 4FC0 C0CF 0000"
$"000F 0C0C 0F77 7F0C 0F44 4F0C 0C0F 0000"
$"000F C0C0 CF77 7FC0 CF44 4FC0 C0CF 0000"
$"000F 0C0C 0F77 7F0C 0F44 4F0C 0C0F 0000"
$"000F C0C0 CFFF FFC0 CF44 4FC0 C0CF 0000"
$"000F 0C0C 0C0C 0C0C 0F44 4F0C 0C0F 0000"
$"000F C0C0 C0C0 C0C0 CF44 4FC0 C0CF 0000"
$"000F 0C0C 0C0C 0CFF FF44 4FFF FC0F 0000"
$"000F C0C0 C0C0 C0CF 4444 444F C0CF 0000"
$"000F 0C0C 0C0C 0C0C F444 44FC 0C0F 0000"
$"000F C0C0 C0C0 C0C0 CF44 4FC0 C0CF 0000"
$"000F 0C0C 0C0C 0C0C 0CF4 FC0C 0C0F 0000"
$"000F C0C0 C0C0 C0C0 C0CF C0C0 C0CF 0000"
$"000F 0C0C 0C0C 0C0C 0C0C 0C0C 0C0F 0000"
$"000F C0C0 C0C0 C0C0 C0C0 C0C0 C0CF 0000"
$"000F FFFF FFFF FFFF FFFF FFFF FFFF 0000"
};
data 'ics#' (128) {
$"0110 02A8 0444 08EE 1028 2028 5C2C 942A"
$"5429 343A 1404 1408 7710 2220 1540 0880"
$"0110 03B8 07FC 0FFE 1FF8 3FF8 7FFC FFFE"
$"7FFF 3FFE 1FFC 1FF8 7FF0 3FE0 1DC0 0880"
};
data 'ics#' (129) {
$"7FE0 4430 4A28 513C 7B84 4A04 4AE4 4AA4"
$"4AA4 4EA4 40A4 43BC 4114 40A4 4044 7FFC"
$"7FE0 7FF0 7FF8 7FFC 7FFC 7FFC 7FFC 7FFC"
$"7FFC 7FFC 7FFC 7FFC 7FFC 7FFC 7FFC 7FFC"
};
data 'ics8' (128) {
$"0000 0000 0000 00FF 0000 00FF 0000 0000"
$"0000 0000 0000 FF9F FF00 FFC0 FF00 0000"
$"0000 0000 00FF 9F92 9FFF C0C0 C0FF 0000"
$"0000 0000 FF9F 929F FFFF FFC0 FFFF FF00"
$"0000 00FF 9F92 9F92 9F92 FFC0 FF00 0000"
$"0000 FF9F 929F 929F 929F FFC0 FF00 0000"
$"00FF 9FFF FFFF 9F92 9F92 FFC0 FFFF 0000"
$"FF9F 92FF 69FF 929F 929F FFC0 FF9F FF00"
$"00FF 9FFF 69FF 9F92 9F92 FFC0 FF92 9FFF"
$"0000 FFFF 69FF 929F 929F FFFF FF9F FF00"
$"0000 00FF 69FF 9F92 9F92 9F92 9FFF 0000"
$"0000 00FF 69FF 929F 929F 929F FF00 0000"
$"00FF FFFF 69FF FFFF 9F92 9FFF 0000 0000"
$"0000 FF69 6969 FF9F 929F FF00 0000 0000"
$"0000 00FF 69FF 00FF 9FFF 0000 0000 0000"
$"0000 0000 FF00 0000 FF00 0000 0000 0000"
};
data 'ics8' (129) {
$"00FF FFFF FFFF FFFF FFFF FF00 0000 0000"
$"00FF F5F5 F5FF F5F5 F5F5 FFFF 0000 0000"
$"00FF F5F5 FFC0 FFF5 F5F5 FF2B FF00 0000"
$"00FF F5FF C0C0 C0FF F5F5 FFFF FFFF 0000"
$"00FF FFFF FFC0 FFFF FFF5 F5F5 F5FF 0000"
$"00FF F5F5 FFC0 FFF5 F5F5 F5F5 F5FF 0000"
$"00FF F5F5 FFC0 FFF5 FFFF FFF5 F5FF 0000"
$"00FF F5F5 FFC0 FFF5 FF69 FFF5 F5FF 0000"
$"00FF F5F5 FFC0 FFF5 FF69 FFF5 F5FF 0000"
$"00FF F5F5 FFFF FFF5 FF69 FFF5 F5FF 0000"
$"00FF F5F5 F5F5 F5F5 FF69 FFF5 F5FF 0000"
$"00FF F5F5 F5F5 FFFF FF69 FFFF FFFF 0000"
$"00FF F5F5 F5F5 F5FF 6969 69FF F5FF 0000"
$"00FF F5F5 F5F5 F5F5 FF69 FFF5 F5FF 0000"
$"00FF F5F5 F5F5 F5F5 F5FF F5F5 F5FF 0000"
$"00FF FFFF FFFF FFFF FFFF FFFF FFFF 0000"
};
data 'ics4' (128) {
$"0000 000F 000F 0000 0000 00F8 F0F7 F000"
$"0000 0F88 8F77 7F00 0000 F888 FFF7 FFF0"
$"000F 8888 88F7 F000 00F8 8888 88F7 F000"
$"0F8F FF88 88F7 FF00 F88F 4F88 88F7 F8F0"
$"0F8F 4F88 88F7 F88F 00FF 4F88 88FF F8F0"
$"000F 4F88 8888 8F00 000F 4F88 8888 F000"
$"0FFF 4FFF 888F 0000 00F4 44F8 88F0 0000"
$"000F 4F0F 8F00 0000 0000 F000 F000 0000"
};
data 'ics4' (129) {
$"0FFF FFFF FFF0 0000 0F0C 0F0C 0CFF 0000"
$"0FC0 F7F0 C0FC F000 0F0F 777F 0CFF FF00"
$"0FFF F7FF F0C0 CF00 0F0C F7FC 0C0C 0F00"
$"0FC0 F7F0 FFF0 CF00 0F0C F7FC F4FC 0F00"
$"0FC0 F7F0 F4F0 CF00 0F0C FFFC F4FC 0F00"
$"0FC0 C0C0 F4F0 CF00 0F0C 0CFF F4FF FF00"
$"0FC0 C0CF 444F CF00 0F0C 0C0C F4FC 0F00"
$"0FC0 C0C0 CFC0 CF00 0FFF FFFF FFFF FF00"
};

278
macos/main.c Normal file
View File

@ -0,0 +1,278 @@
#include "main.h"
#include "error.h"
#include "project.h"
#include "resources.h"
#ifndef __MWERKS__
// Link error if defined with CodeWarrior.
QDGlobals qd;
#endif
void QuitApp(void)
{
ExitToShell();
}
static void HandleClose(WindowRef window)
{
ProjectHandle project;
if (window == NULL) {
return;
}
switch (GetWindowKind(window)) {
case kApplicationWindowKind:
project = (ProjectHandle)GetWRefCon(window);
ASSERT(project != NULL);
ProjectCommand(window, project, rMENU_File, iFile_Close);
break;
}
}
static void AdjustMenus(void)
{
WindowRef window;
MenuHandle fileMenu;
ProjectHandle project;
Boolean hasDocument = FALSE;
fileMenu = GetMenuHandle(rMENU_File);
window = FrontWindow();
if (window != NULL) {
switch (GetWindowKind(window)) {
case kApplicationWindowKind:
project = (ProjectHandle)GetWRefCon(window);
ASSERT(project != NULL);
hasDocument = TRUE;
ProjectAdjustMenus(project, fileMenu);
break;
}
}
if (fileMenu != NULL) {
EnableItem(fileMenu, iFile_New);
DisableItem(fileMenu, iFile_Open);
if (!hasDocument) {
DisableItem(fileMenu, iFile_Close);
DisableItem(fileMenu, iFile_Save);
DisableItem(fileMenu, iFile_SaveAs);
DisableItem(fileMenu, iFile_Revert);
}
EnableItem(fileMenu, iFile_Quit);
}
}
static void HandleMenuCommand(long command)
{
int menuID = command >> 16;
int item = command & 0xffff;
WindowRef window;
ProjectHandle project;
switch (menuID) {
case rMENU_Apple:
switch (item) {
case iApple_Info:
return;
default: {
Str255 itemName;
GetMenuItemText(GetMenuHandle(rMENU_Apple), item, itemName);
OpenDeskAcc(itemName);
}
return;
}
return;
case rMENU_File:
switch (item) {
case iFile_New:
ProjectNew();
return;
case iFile_Quit:
QuitApp();
return;
}
break;
case rMENU_Edit:
break;
}
window = FrontWindow();
if (window != NULL) {
switch (GetWindowKind(window)) {
case kApplicationWindowKind:
project = (ProjectHandle)GetWRefCon(window);
ASSERT(project != NULL);
ProjectCommand(window, project, menuID, item);
return;
}
}
SysBeep(1);
}
// Handle a mouse down event.
static void HandleMouseDown(EventRecord *event)
{
short clickPart;
WindowPtr window;
ProjectHandle project;
clickPart = FindWindow(event->where, &window);
switch (clickPart) {
// case inDesk:
case inMenuBar:
AdjustMenus();
HandleMenuCommand(MenuSelect(event->where));
HiliteMenu(0);
break;
case inSysWindow:
SystemClick(event, window);
break;
case inContent:
if (window != FrontWindow()) {
SelectWindow(window);
} else {
switch (GetWindowKind(window)) {
case kApplicationWindowKind:
project = (ProjectHandle)GetWRefCon(window);
ASSERT(project != NULL);
ProjectMouseDown(window, project, event);
break;
}
}
break;
case inDrag:
if ((event->modifiers & cmdKey) != 0 && window != FrontWindow()) {
SelectWindow(window);
}
DragWindow(window, event->where, &(*GetGrayRgn())->rgnBBox);
break;
// case inGrow:
case inGoAway:
if (TrackGoAway(window, event->where)) {
HandleClose(window);
}
break;
// case inZoomIn:
// case inZoomOut:
// case inCollapseBox:
// case inProxyIcon:
}
}
static void HandleKeyDown(EventRecord *event)
{
long command;
if ((event->modifiers & cmdKey) != 0) {
AdjustMenus();
command = MenuKey(event->message & charCodeMask);
HandleMenuCommand(command);
HiliteMenu(0);
}
}
static void HandleActivate(EventRecord *event)
{
ProjectHandle project;
WindowRef window;
GrafPtr savedPort;
int isActive;
window = (WindowRef)event->message;
GetPort(&savedPort);
SetPort(window);
isActive = (event->modifiers & activeFlag) != 0;
switch (GetWindowKind(window)) {
case kApplicationWindowKind:
project = (ProjectHandle)GetWRefCon(window);
ASSERT(project != NULL);
ProjectActivate(window, project, isActive);
break;
}
SetPort(savedPort);
}
static void HandleUpdate(EventRecord *event)
{
ProjectHandle project;
WindowPtr window;
GrafPtr savedPort;
window = (WindowPtr)event->message;
GetPort(&savedPort);
SetPort(window);
BeginUpdate(window);
switch (GetWindowKind(window)) {
case kApplicationWindowKind:
project = (ProjectHandle)GetWRefCon(window);
ASSERT(project != NULL);
ProjectUpdate(window, project);
break;
}
EndUpdate(window);
SetPort(savedPort);
}
void main(void)
{
MaxApplZone();
InitGraf(&qd.thePort);
InitFonts();
InitWindows();
InitMenus();
TEInit();
InitDialogs(NULL);
InitCursor();
// Set up menu bar.
{
Handle menuBar;
menuBar = GetNewMBar(rMBAR_Main);
if (menuBar == NULL) {
EXIT_ASSERT(NULL);
}
SetMenuBar(menuBar);
DisposeHandle(menuBar);
AppendResMenu(GetMenuHandle(rMENU_Apple), 'DRVR');
DrawMenuBar();
}
for (;;) {
EventRecord event;
WaitNextEvent(everyEvent, &event, 1, NULL);
switch (event.what) {
case mouseDown:
HandleMouseDown(&event);
break;
// case mouseUp:
case keyDown:
HandleKeyDown(&event);
break;
// case autoKey:
case activateEvt:
HandleActivate(&event);
break;
case updateEvt:
HandleUpdate(&event);
break;
// case diskEvt:
// osEvt:
// case kHighLevelEvent:
}
}
}

21
macos/main.h Normal file
View File

@ -0,0 +1,21 @@
// main.h - Main entry point.
#ifndef MACOS_MAIN_H
#define MACOS_MAIN_H
// Menu item IDs.
enum {
iApple_Info = 1,
iFile_New = 1,
iFile_Open = 2,
iFile_Close = 4,
iFile_Save = 5,
iFile_SaveAs = 6,
iFile_Revert = 7,
iFile_Quit = 9,
};
// Quit the application.
void QuitApp(void);
#endif

56
macos/menu.r Normal file
View File

@ -0,0 +1,56 @@
#include "Menus.r"
#include "resources.h"
resource 'MBAR' (rMBAR_Main) {{
rMENU_Apple,
rMENU_File,
rMENU_Edit,
}};
resource 'MENU' (rMENU_Apple, preload) {
rMENU_Apple,
textMenuProc,
0b01111111111111111111111111111101,
enabled,
apple,
{
"About " APPNAME "…", noIcon, noKey, noMark, plain,
"-", noIcon, noKey, noMark, plain,
}
};
resource 'MENU' (rMENU_File, preload) {
rMENU_File,
textMenuProc,
0b01111111111111111111111101111011,
enabled,
"File",
{
"New", noIcon, "N", noMark, plain,
"Open…", noIcon, "O", noMark, plain,
"-", noIcon, noKey, noMark, plain,
"Close", noIcon, "W", noMark, plain,
"Save", noIcon, "S", noMark, plain,
"Save As…", noIcon, noKey, noMark, plain,
"Revert", noIcon, noKey, noMark, plain,
"-", noIcon, noKey, noMark, plain,
"Quit", noIcon, "Q", noMark, plain,
}
};
resource 'MENU' (rMENU_Edit, preload) {
rMENU_Edit,
textMenuProc,
0b01111111111111111111111111111101,
enabled,
"Edit",
{
"Undo", noIcon, "Z", noMark, plain,
"-", noIcon, noKey, noMark, plain,
"Cut", noIcon, "X", noMark, plain,
"Copy", noIcon, "C", noMark, plain,
"Paste", noIcon, "V", noMark, plain,
"Clear", noIcon, noKey, noMark, plain,
}
};

90
macos/path.c Normal file
View File

@ -0,0 +1,90 @@
#include "path.h"
#include "error.h"
#include <string.h>
enum {
kMaxPath = 512,
};
OSErr GetDirPath(FSSpec *spec, int *pathLength, Handle *path)
{
Handle segments; // Packed sequence of segments, Pascal strings.
int textLen; // Total length of segments, including length byte.
Handle result;
unsigned char *name;
CInfoPBRec ci;
OSErr err;
unsigned char *op;
const unsigned char *ip, *ie;
int length;
// Query target.
memset(&ci, 0, sizeof(ci));
ci.dirInfo.ioNamePtr = spec->name;
ci.dirInfo.ioVRefNum = spec->vRefNum;
ci.dirInfo.ioDrDirID = spec->parID;
err = PBGetCatInfoSync(&ci);
if (err != noErr) {
return err;
}
// FIXME: kioFlAttribDirMask
if ((ci.hFileInfo.ioFlAttrib & ioDirMask) == 0) {
return fnfErr;
}
segments = NULL;
textLen = spec->name[0] + 1;
PtrToHand(spec->name, &segments, textLen);
if (segments == NULL) {
return MemError();
}
// Add name of parent.
while (ci.dirInfo.ioDrParID != fsRtParID) {
ASSERT(textLen < kMaxPath);
SetHandleSize(segments, textLen + 256);
err = MemError();
if (err != noErr) {
goto done;
}
HLock(segments);
name = (unsigned char *)*segments + textLen;
ci.dirInfo.ioNamePtr = name;
ci.dirInfo.ioDrDirID = ci.dirInfo.ioDrParID;
ci.dirInfo.ioFDirIndex = -1;
err = PBGetCatInfoSync(&ci);
HUnlock(segments);
if (err != 0) {
goto done;
}
textLen += name[0] + 1;
}
// Copy segments into handle in reverse order.
result = NewHandle(textLen);
if (result == NULL) {
err = MemError();
goto done;
}
op = (unsigned char *)*result + textLen;
ip = (const unsigned char *)*segments;
ie = ip + textLen;
while (ip < ie) {
op--;
*op = ':';
length = *ip;
op -= length;
BlockMoveData(ip + 1, op, length);
ip += length + 1;
}
*pathLength = textLen;
*path = result;
err = 0;
done:
if (segments != NULL) {
DisposeHandle(segments);
}
return err;
}

8
macos/path.h Normal file
View File

@ -0,0 +1,8 @@
// path.h - Path manipulation utilities.
#ifndef MACOS_PATH_H
#define MACOS_PATH_H
// Get the full path to a directory.
OSErr GetDirPath(FSSpec *spec, int *pathLength, Handle *path);
#endif

530
macos/project.c Normal file
View File

@ -0,0 +1,530 @@
#include "project.h"
#include "choose_directory.h"
#include "crc32.h"
#include "error.h"
#include "main.h"
#include "path.h"
#include "resources.h"
#include "tempfile.h"
#include <Files.h>
#include <MacMemory.h>
#include <MacWindows.h>
#include <string.h>
/*
Project format:
Header:
byte[32] magic
uint32 version
uint32 file size
uint32 data crc32
uint32 chunk count
ckinfo[] chunk info
byte[] chunk data
Chunk info:
byte[8] chunk name
uint32 byte offset
uint32 byte length
Chunks: (names zero-padded)
"s.alias" source directory alias
"d.alias" destination directory alias
Chunks are aligned to 16 byte boundaries, and the file is padded to a 16 byte
boundary. This is just to make it easier to read hexdumps.
*/
// clang-format off
// Magic cookie that identifies project files.
static const kMagic[32] = {
// Identify the file type.
'S', 'y', 'n', 'c', 'F', 'i', 'l', 'e', 's', ' ',
'P', 'r', 'o', 'j', 'e', 'c', 't',
// Detect newline conversions.
0, 10, 0, 13, 0, 13, 10, 0,
// Detect encoding conversions (infinity in Roman and UTF-8).
0xb0, 0, 0xe2, 0x88, 0x9e, 0, 0,
};
// clang-format on
static const kChunkAlias[2][8] = {
{'l', '.', 'a', 'l', 'i', 'a', 's', 0},
{'r', '.', 'a', 'l', 'i', 'a', 's', 0},
};
struct ProjectHeader {
UInt8 magic[32];
UInt32 version;
UInt32 size;
UInt32 crc32;
UInt32 chunkCount;
};
struct ProjectChunk {
UInt8 name[8];
UInt32 offset;
UInt32 size;
};
// Dimensions of controls.
enum {
kVBorder = 10,
kHBorder = 10,
kDirVSize = 62,
};
enum {
// Base ID for aliases.
rAliasBase = 128,
};
enum {
kControlChooseDirLocal,
kControlChooseDirRemote,
};
// FIXME: this is redundant.
enum {
kDirLocal,
kDirRemote,
};
struct ProjectDir {
int pathLength;
Handle path;
AliasHandle alias;
};
// A synchronization project.
struct Project {
int windowWidth;
int isActive;
// File reference to the project file, or fileRef == 0 if file not saved.
short fileRef;
FSSpec fileSpec;
struct ProjectDir dirs[2];
};
void ProjectNew(void)
{
ProjectHandle project;
WindowRef window;
ControlRef control;
struct Project *projectp;
int i, windowWidth, controlWidth;
project = (ProjectHandle)NewHandle(sizeof(struct Project));
if (project == NULL) {
EXIT_ASSERT(NULL);
}
window = GetNewCWindow(rWIND_Project, NULL, (WindowPtr)-1);
if (window == NULL) {
EXIT_ASSERT(NULL);
}
windowWidth = window->portRect.right - window->portRect.left;
SetWRefCon(window, (long)project);
projectp = *project;
memset(projectp, 0, sizeof(*projectp));
projectp->windowWidth = windowWidth;
for (i = 0; i < 2; i++) {
control = GetNewControl(rCNTL_ChooseFolder, window);
if (control == NULL) {
EXIT_ASSERT(NULL);
}
controlWidth =
(*control)->contrlRect.right - (*control)->contrlRect.left;
MoveControl(control, windowWidth - controlWidth - kHBorder,
kVBorder + kDirVSize * i + 22);
SetControlReference(control, kControlChooseDirLocal + i);
}
ShowWindow(window);
}
void ProjectAdjustMenus(ProjectHandle project, MenuHandle fileMenu)
{
(void)project;
if (fileMenu != NULL) {
EnableItem(fileMenu, iFile_Close);
EnableItem(fileMenu, iFile_Save);
EnableItem(fileMenu, iFile_SaveAs);
EnableItem(fileMenu, iFile_Revert);
}
}
static void ProjectClose(WindowRef window, ProjectHandle project)
{
struct Project *projectp;
int i;
KillControls(window);
DisposeWindow(window);
HLock((Handle)project);
projectp = *project;
if (projectp->fileRef != 0) {
FSClose(projectp->fileRef);
}
for (i = 0; i < 2; i++) {
if (projectp->dirs[i].path != NULL) {
DisposeHandle(projectp->dirs[i].path);
}
}
DisposeHandle((Handle)project);
}
#define kHeaderSize 48
#define kChunkInfoSize 16
#define kChunkCount 2
#define ALIGN(x) (((x) + 15) + ~(UInt32)15)
struct ProjectData {
Handle data;
Size size;
};
static OSErr ProjectMarshal(ProjectHandle project, struct ProjectData *datap)
{
struct ProjectChunk ckInfo[kChunkCount];
Handle ckData[kChunkCount];
UInt32 size, pos, ckOff, ckSize;
Handle h;
int i;
struct ProjectHeader *hdr;
size = kHeaderSize + kChunkCount * kChunkInfoSize;
for (i = 0; i < 2; i++) {
h = (Handle)(*project)->dirs[i].alias;
ckSize = GetHandleSize(h);
memcpy(ckInfo[i].name, kChunkAlias[i], 8);
ckInfo[i].offset = size;
ckInfo[i].size = ckSize;
ckData[i] = h;
size = ALIGN(size + ckSize);
}
h = NewHandle(size);
if (h == NULL) {
return MemError();
}
hdr = (void *)*h;
memcpy(hdr->magic, kMagic, sizeof(kMagic));
hdr->version = 0x10000;
hdr->size = size;
hdr->chunkCount = kChunkCount;
memcpy(*h + kHeaderSize, ckInfo, sizeof(ckInfo));
pos = kHeaderSize + kChunkCount * kChunkInfoSize;
for (i = 0; i < kChunkCount; i++) {
ckOff = ckInfo[i].offset;
ckSize = ckInfo[i].size;
memset(*h + pos, 0, ckOff - pos);
memcpy(*h + ckOff, *ckData[i], ckSize);
pos = ckOff + ckSize;
}
memcpy(*h + pos, 0, size - pos);
datap->data = h;
datap->size = size;
return noErr;
}
// Prompt the user for a location to save the project.
static void ProjectSaveAs(WindowRef window, ProjectHandle project)
{
// See listing 1-12 in IM: Files. This has some differences.
Str255 name;
StandardFileReply reply;
short refNum;
struct ProjectData data;
struct Project *projectp;
OSErr err;
Boolean hasfile;
ParamBlockRec pb;
struct ErrorParams errp;
long size;
data.data = NULL;
refNum = 0;
hasfile = FALSE;
GetWTitle(window, name);
StandardPutFile("\pSave project as:", name, &reply);
if (!reply.sfGood) {
return;
}
err = ProjectMarshal(project, &data);
if (err != noErr) {
goto error;
}
// Delete file if replacing. This way, we get a new creation date (makes
// sense) and the correct creator and type codes.
if (reply.sfReplacing) {
err = FSpDelete(&reply.sfFile);
if (err != noErr) {
goto error;
}
}
// Write out the new file.
err = FSpCreate(&reply.sfFile, kCreator, kTypeProject, reply.sfScript);
if (err != noErr) {
goto error;
}
hasfile = TRUE;
err = FSpOpenDF(&reply.sfFile, fsWrPerm, &refNum);
if (err != noErr) {
goto error;
}
size = data.size;
err = FSWrite(refNum, &size, *data.data);
if (err != noErr) {
goto error;
}
DisposeHandle(data.data);
data.data = NULL;
memset(&pb, 0, sizeof(pb));
pb.fileParam.ioFRefNum = refNum;
PBFlushFile(&pb, FALSE);
err = pb.fileParam.ioResult;
if (err != noErr) {
goto error;
}
// Done writing, update the window and project.
SetWTitle(window, reply.sfFile.name);
projectp = *project;
if (projectp->fileRef != 0) {
FSClose(projectp->fileRef);
}
projectp->fileRef = refNum;
projectp->fileSpec = reply.sfFile;
return;
error:
if (data.data != NULL) {
DisposeHandle(data.data);
}
if (refNum != 0) {
FSClose(refNum);
}
if (hasfile) {
FSpDelete(&reply.sfFile);
}
memset(&errp, 0, sizeof(errp));
errp.err = kErrCouldNotSaveProject;
errp.osErr = err;
errp.strParam = reply.sfFile.name;
ShowError(&errp);
}
static void ProjectSaveImpl(WindowRef window, ProjectHandle project)
{
struct ProjectData data;
short refNum;
FSSpec temp;
OSErr err;
struct ErrorParams errp;
Boolean hasfile;
long size;
(void)window;
data.data = NULL;
refNum = 0;
hasfile = FALSE;
err = ProjectMarshal(project, &data);
if (err != noErr) {
goto error;
}
err = MakeTempFile((*project)->fileSpec.vRefNum, &temp);
if (err != noErr) {
goto error;
}
err = FSpCreate(&temp, 'trsh', 'trsh', smSystemScript);
if (err != noErr) {
goto error;
}
hasfile = TRUE;
err = FSpOpenDF(&temp, fsWrPerm, &refNum);
if (err != noErr) {
goto error;
}
size = data.size;
err = FSWrite(refNum, &size, *data.data);
if (err != noErr) {
goto error;
}
DisposeHandle(data.data);
data.data = NULL;
err = FSClose(refNum);
refNum = 0;
if (err != noErr) {
goto error;
}
err = FSpExchangeFiles(&temp, &(*project)->fileSpec);
if (err != noErr) {
goto error;
}
FSpDelete(&temp);
return;
error:
if (data.data) {
DisposeHandle(data.data);
}
if (refNum != 0) {
FSClose(refNum);
}
if (hasfile) {
FSpDelete(&temp);
}
memcpy(&temp.name, &(*project)->fileSpec.name, sizeof(temp.name));
memset(&errp, 0, sizeof(errp));
errp.err = kErrCouldNotSaveProject;
errp.osErr = err;
errp.strParam = temp.name;
ShowError(&errp);
}
// Save the project to disk in response to a Save command.
static void ProjectSave(WindowRef window, ProjectHandle project)
{
if ((*project)->fileRef == 0) {
ProjectSaveAs(window, project);
} else {
ProjectSaveImpl(window, project);
}
}
void ProjectCommand(WindowRef window, ProjectHandle project, int menuID,
int item)
{
switch (menuID) {
case rMENU_File:
switch (item) {
case iFile_Close:
ProjectClose(window, project);
break;
case iFile_Save:
ProjectSave(window, project);
break;
case iFile_SaveAs:
ProjectSaveAs(window, project);
break;
}
break;
}
}
void ProjectUpdate(WindowRef window, ProjectHandle project)
{
struct Project *projectp;
Rect rect;
Handle text;
int i;
HLock((Handle)project);
projectp = *project;
rect = window->portRect;
EraseRect(&rect);
MoveTo(0, kVBorder + kDirVSize - 10);
LineTo(projectp->windowWidth, kVBorder + kDirVSize - 10);
for (i = 0; i < 2; i++) {
MoveTo(kHBorder, kVBorder + kDirVSize * i + 12);
DrawString(i == 0 ? "\pLocal Folder" : "\pRemote Folder");
text = projectp->dirs[i].path;
if (text != NULL) {
HLock(text);
MoveTo(kHBorder, kVBorder + kDirVSize * i + 36);
DrawText(*text, 0, projectp->dirs[i].pathLength);
HUnlock(text);
}
}
UpdateControls(window, window->visRgn);
HUnlock((Handle)project);
}
void ProjectActivate(WindowRef window, ProjectHandle project, int isActive)
{
struct Project *projectp;
projectp = *project;
if (projectp->isActive != isActive) {
projectp->isActive = isActive;
InvalRect(&window->portRect);
}
}
static void ProjectChooseDir(WindowRef window, ProjectHandle project,
int whichDir)
{
struct Project *projectp;
struct ProjectDir *dirp;
FSSpec directory;
Handle path;
int pathLength;
OSErr err;
if (ChooseDirectory(&directory)) {
err = GetDirPath(&directory, &pathLength, &path);
if (err != noErr) {
ExitErrorOS(kErrUnknown, err);
}
projectp = *project;
dirp = &projectp->dirs[whichDir];
if (dirp->path != NULL) {
DisposeHandle(dirp->path);
}
dirp->pathLength = pathLength;
dirp->path = path;
InvalRect(&window->portRect);
}
}
void ProjectMouseDown(WindowRef window, ProjectHandle project,
EventRecord *event)
{
Point mouse;
GrafPtr savedPort;
ControlRef control;
ControlPartCode part;
long reference;
GetPort(&savedPort);
SetPort(window);
mouse = event->where;
GlobalToLocal(&mouse);
part = FindControl(mouse, window, &control);
switch (part) {
case kControlButtonPart:
reference = GetControlReference(control);
if (TrackControl(control, mouse, NULL) != 0) {
switch (reference) {
case kControlChooseDirLocal:
ProjectChooseDir(window, project, kDirLocal);
break;
case kControlChooseDirRemote:
ProjectChooseDir(window, project, kDirRemote);
break;
}
}
break;
}
SetPort(savedPort);
}

28
macos/project.h Normal file
View File

@ -0,0 +1,28 @@
// project.h - Synchronization projects.
#ifndef MACOS_PROJECT_H
#define MACOS_PROJECT_H
// Handle to a synchronization project.
typedef struct Project **ProjectHandle;
// Create a new empty synchronization project.
void ProjectNew(void);
// Adjust menus before selecting a menu item.
void ProjectAdjustMenus(ProjectHandle project, MenuHandle fileMenu);
// Handle a menu command or equivalent.
void ProjectCommand(WindowRef window, ProjectHandle project, int menuID,
int item);
// Handle an update event for a project window.
void ProjectUpdate(WindowRef window, ProjectHandle project);
// Set whether a project window is active.
void ProjectActivate(WindowRef window, ProjectHandle project, int isActive);
// Handle a mouse down event in a window.
void ProjectMouseDown(WindowRef window, ProjectHandle project,
EventRecord *event);
#endif

32
macos/resources.h Normal file
View File

@ -0,0 +1,32 @@
#ifndef MACOS_RESOURCES_H
#define MACOS_RESOURCES_H
#define APPNAME "SyncFiles"
#define kCreator 'SyFl'
#define kTypeProject 'SyPr'
/* ALRT Error dialog */
#define rAlrtError 128
/* CNTL Controls */
#define rCNTL_ChooseFolder 128
/* DLOG Dialogs */
#define rDLOG_OpenFolder 128
/* MBAR Menu bar */
#define rMBAR_Main 128
/* MENU Menus */
#define rMENU_Apple 128
#define rMENU_File 129
#define rMENU_Edit 130
/* STR# Error messages. */
#define rSTRS_Errors 128
/* WIND Windows */
#define rWIND_Project 128
#endif

147
macos/resources.r Normal file
View File

@ -0,0 +1,147 @@
#include "Controls.r"
#include "Dialogs.r"
#include "Finder.r"
#include "MacTypes.r"
#include "Processes.r"
#include "resources.h"
resource 'ALRT' (rAlrtError, purgeable) {
{ 40, 40, 141, 360 },
128, /* DITL ID */
{
OK, visible, sound1,
OK, visible, sound1,
OK, visible, sound1,
OK, visible, sound1,
},
centerMainScreen
};
/* See Macintosh Human Interface Guidelines (1992) p. 197 "Basic Dialog Box Layout" */
/* Note that there are an extra 3px of margin for "free" on the dialog border. */
resource 'DITL' (128, purgeable) {{
/* Quit button: 13px margin */
{ 71, 240, 91, 310 },
Button { enabled, "Quit" },
/* Error text: 13px margin, 3 lines @ 16px line height */
{ 10, 75, 58, 310 },
StaticText { enabled, "^0" },
/* Icon: 13px vertical, 23px horizontal */
{ 10, 20, 42, 52 },
Icon { enabled, 0 },
}};
resource 'DLOG' (rDLOG_OpenFolder, purgeable) {
/* Height += 31 */
{0, 0, 197, 344},
dBoxProc,
invisible,
noGoAway,
0,
129,
"",
noAutoCenter,
};
/* Open Folder, based on system -6042 */
resource 'DITL' (129, purgeable) {{
/* Standard file (do not change, see IM: Files 3-18) */
{135, 252, 155, 332},
Button {enabled, "Open"},
{104, 252, 124, 332},
Button {enabled, "Cancel"},
{0, 0, 0, 0},
HelpItem {disabled, HMScanhdlg {-6042}},
{8, 235, 24, 337},
UserItem {enabled},
{32, 252, 52, 332},
Button {enabled, "Eject"},
{60, 252, 80, 332},
Button {enabled, "Desktop"},
{29, 12, 159, 230},
UserItem {enabled},
{6, 12, 25, 230},
UserItem {enabled},
{91, 251, 92, 333},
Picture {disabled, 11},
/* Additions */
{166, 252, 186, 332},
Button {enabled, "Choose"},
{166, 12, 186, 230},
Button {enabled, "Choose Current Directory"},
}};
#ifdef MPW
resource 'SIZE' (-1) {
dontSaveScreen,
acceptSuspendResumeEvents,
enableOptionSwitch,
canBackground,
multiFinderAware,
backgroundAndForeground,
dontGetFrontClicks,
ignoreChildDiedEvents,
is32BitCompatible,
notHighLevelEventAware,
onlyLocalHLEvents,
notStationeryAware,
dontUseTextEditServices,
notDisplayManagerAware,
reserved,
reserved,
60 * 1024,
60 * 1024
};
#endif
type kCreator as 'STR ';
resource kCreator (0, purgeable) {
APPNAME " 0.1 ©2022 Dietrich Epp"
};
resource 'vers' (1) {
0x00, 0x01, development, 0x00,
verUS,
"0.1",
"0.1, Copyright ©2021 Dietrich Epp"
};
resource 'FREF' (128, purgeable) {'APPL', 0, ""};
resource 'FREF' (129, purgeable) { kTypeProject, 1, ""};
resource 'BNDL' (128, purgeable) {
kCreator,
0,
{
'ICN#', {0, 128, 1, 129},
'FREF', {0, 128, 1, 129},
},
};
resource 'STR#' (rSTRS_Errors) {{
"An unknown error occurred.",
"An internal error occurred.",
"Out of memory.",
"Could not save project “^1”.",
}};
resource 'WIND' (rWIND_Project, preload, purgeable) {
{32, 32, 256, 32 + 128*3},
documentProc,
invisible,
goAway,
0,
"Untitled Project",
staggerMainScreen,
};
resource 'CNTL' (rCNTL_ChooseFolder, preload, purgeable) {
{10, 10, 30, 90},
0,
visible,
1,
0,
pushButProc,
0,
"Choose…",
};

135
macos/strutil.c Normal file
View File

@ -0,0 +1,135 @@
#include "strutil.h"
#include "error.h"
#include <NumberFormatting.h>
#include <stdarg.h>
#include <string.h>
static unsigned char *StrPtrAppend(unsigned char *ptr, unsigned char *end,
const unsigned char *str)
{
unsigned i, len, rem;
len = str[0];
rem = end - ptr;
if (len > rem) {
len = rem;
}
str++;
for (i = 0; i < len; i++) {
*ptr++ = *str++;
}
return ptr;
}
static void StrAppendVFormat(unsigned char *dest, const unsigned char *msg,
va_list ap)
{
const unsigned char *mptr = msg + 1, *mend = mptr + msg[0];
unsigned char *ptr = dest + 1 + dest[0], *end = dest + 256;
unsigned char c, temp[16];
const unsigned char *pstrvalue;
int ivalue;
next:
while (mptr < mend) {
c = *mptr++;
if (c == '%' && mptr < mend) {
switch (*mptr) {
case '%':
mptr++;
break;
case 'd':
mptr++;
ivalue = va_arg(ap, int);
NumToString(ivalue, temp);
ptr = StrPtrAppend(ptr, end, temp);
goto next;
case 'S':
mptr++;
pstrvalue = va_arg(ap, const unsigned char *);
ptr = StrPtrAppend(ptr, end, pstrvalue);
goto next;
}
}
if (ptr == end) {
break;
}
*ptr++ = c;
}
*dest = ptr - dest - 1;
}
void StrFormat(unsigned char *dest, const unsigned char *msg, ...)
{
va_list ap;
dest[0] = 0;
va_start(ap, msg);
StrAppendVFormat(dest, msg, ap);
va_end(ap);
}
void StrAppendFormat(unsigned char *dest, const unsigned char *msg, ...)
{
va_list ap;
va_start(ap, msg);
StrAppendVFormat(dest, msg, ap);
va_end(ap);
}
void StrCopy(unsigned char *dest, int dest_size, unsigned char *src)
{
int len = src[0] + 1;
if (len > dest_size) {
EXIT_ASSERT(NULL);
}
BlockMoveData(src, dest, len);
}
struct StrRef {
const unsigned char *src;
int len;
};
void StrSubstitute(unsigned char *str, const unsigned char *param)
{
int i, n, end;
struct StrRef refs[3];
n = str[0];
for (i = 0; i < n - 1; i++) {
if (str[i + 1] == '^' && str[i + 2] == '1') {
refs[0].src = str + 1;
refs[0].len = i;
refs[1].src = param + 1;
refs[1].len = param[0];
refs[2].src = str + i + 3;
refs[2].len = n - i - 2;
goto concat;
}
}
return;
concat:
n = 0;
for (i = 0; i < 3; i++) {
n += refs[i].len;
}
str[0] = n > 255 ? 255 : n;
for (i = 3; i > 0;) {
i--;
end = n;
n -= refs[i].len;
if (end > 255) {
end = 255;
}
if (n < end) {
memmove(str + 1 + n, refs[i].src, end - n);
}
}
}

22
macos/strutil.h Normal file
View File

@ -0,0 +1,22 @@
// strutil.h - String utilities.
#ifndef MACOS_STRUTIL_H
#define MACOS_STRUTIL_H
// Write a formatted string.
void StrFormat(unsigned char *dest, const unsigned char *msg, ...);
// Append a formatted string to another string.
//
// %% - literal %
// %d - int argument
// %S - Pascal string argument
void StrAppendFormat(unsigned char *dest, const unsigned char *msg, ...);
// Copy a Pascal string. Aborts the program if the string does not fit in the
// destination buffer.
void StrCopy(unsigned char *dest, int dest_size, unsigned char *src);
// Substitute ^1 in the string with param.
void StrSubstitute(unsigned char *str, const unsigned char *param);
#endif

50
macos/tempfile.c Normal file
View File

@ -0,0 +1,50 @@
#include "tempfile.h"
#include <string.h>
// Filename prefix for temporary files.
static const char kTempPrefix[] = "SyncFiles.";
// Hexadecimal digits.
static const char kHexDigits[16] = {'0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
static void U32ToHex(unsigned char *ptr, UInt32 x)
{
int i;
for (i = 0; i < 8; i++) {
ptr[7 - i] = kHexDigits[x & 15];
x >>= 4;
}
}
OSErr MakeTempFile(short vRefNum, FSSpec *spec)
{
short tempVRefNum;
long tempParID;
unsigned long time;
OSErr err;
int pos;
unsigned char name[32];
// This is inspired by IM: Files listing 1-10, page 1-25.
// Generate hopefully unique name.
GetDateTime(&time);
memcpy(name + 1, kTempPrefix, sizeof(kTempPrefix) - 1);
pos = sizeof(kTempPrefix);
U32ToHex(name + pos, time);
pos += 8;
name[0] = pos - 1;
err = FindFolder(vRefNum, kTemporaryFolderType, TRUE, &tempVRefNum,
&tempParID);
if (err != noErr) {
return err;
}
err = FSMakeFSSpec(tempVRefNum, tempParID, name, spec);
if (err != noErr && err != fnfErr) {
return err;
}
return noErr;
}

8
macos/tempfile.h Normal file
View File

@ -0,0 +1,8 @@
#ifndef MACOS_TEMPFILE_H
#define MACOS_TEMPFILE_H
// Create a FSSpec for a temporary file on the given volume. Does not actually
// create the file.
OSErr MakeTempFile(short vRefNum, FSSpec *spec);
#endif