Hello cyberpals.
I thought I would help get your mind off of the US elections
by doing some classic Mac programming.
Last week we ported diff from OpenBSD and
quickly got it running where it can spit out a diff,
and that was just using printf statements which Think C's
ANSI library hopefully prints on its
own console window that it sets up for us.
But that was not a very useful utility
because the paths of the files were hard coded
and you can't select any text or scroll or do anything like
that. So since then I've been turning it into a GUI
and this is the state of the GUI now. So this is the standalone program.
When you start it up, you have two text boxes
and it remembers the last
files that you chose, and so you can just
run a diff and you get a nice status message
at the bottom while it runs and the
cursor changes to a wrist watch.
And it's still kind of slow because diff
is slow or I guess OpenBSD's diff is slow, but you don't
really notice it on a modern computer.
So it spits the diff out pretty much the
same except for it's
in a TextEdit control that we have
attached a scrollbar too, so we can scroll around and
read the diff,
and we get correct
timestamps now of each file and
newlines are converted. I can't remember if
I did that in the last video or not.
And tabs are converted into spaces and
the amount of spaces that it converts
to is in the resource file for the executable so you
can change it if you wanted to.
And we have an About menu
and a Quit menu. So that's
pretty much the state of program. It's pretty useful
as it is now. You can browse
around and select a different file. You can
also choose a directory so I can
do like a backup-- an old Carl backup--
here, I guess. So you
can choose a directory and then a directory, oh and
it should error if one's a directory and one's a file.
And it does.
It will now recurse and show you the status of each file
as it goes through the directory.
So I did all this kind of off-camera because it was
a lot of work and a lot of it involved Mac-specific
filesystem stuff that I had no idea how to do. So I had to
look it up and finding code examples was pretty difficult,
so there was a lot of guessing and just
kind of very slowly iterating on it. But I'm
going to walk through the code today and release it in case
you want to download it and play with the program or the
source code.
And-- let me just let this finish here. I also
want to add an Edit menu here because
you can highlight text right now in the TextEdit control,
which is built into the TextEdit control, but you can't copy
anything out of it either with a menu option
or Command+C. So I would like to add
that, maybe a Select All command, too.
So-- I should maybe have it clear the text box between diffs.
So you get a status, seven files have
changed between these folders, and I'm not sure why it's not reflecting there.
Oh, no. Yeah. Oh, did it
not? It's not clearing the text box.
That's why thought it was, but it's just depending it.
Okay. So that's another bug we need to fix, but obviously you
can see here that it's doing multiple files here,
so you can diff a whole backup of
your code to your current code and see what you've changed.
So that's exactly what I wanted. So let's jump into the code.
A lot of the GUI code just came straight from
Carl because it's a similar layout of a
window with a status bar at the bottom and TextEdit control.
And the hardest thing,
the thing that took me the longest to accomplish was
being able to select a directory in the
standard dialog.
The function is SFGetFile,
and that just opens up the standard dialog
here where you can browse around and choose the file and stuff.
But the way that it works by default is if you-- so I
will use the THINK C one. So if you browse around and
you select a directory and click Open, by default,
what it does is it assumes that you want to browse into
that directory the same way if you double click on a directory.
So to be able to highlight a directory and click Open,
that required some custom code, which took me a long time to figure out.
The only place that I was able to find a mention on it was
like FAQ from some old Usenet group. And of
course, the answer was like, "yeah, you can do it, but it's
too long to explain here. Download some sample
code from Apple's FTP site", which of course, is not there anymore.
And every archive I could find on the FTP site did
not have the file that was mentioned, so I basically had to
figure it out on my own. And before I
get into that, I want to explain something
that if you've been following along with my code and
especially the demo application that I showed,
I made a mistake which somebody
came in the #cyberpals IRC channel and corrected me on it.
And after looking into it, I realized that they were correct.
So, I think-- let me just find their name.
nonzero-- Anthony, I don't know if you want me to
use your last name, but thank you, Anthony, for finding
this and letting me know. So basically, what the mistake is is
you get an event in your event loop and you call
FindWindow on it and you pass a pointer and then FindWindow
looks up the window where the event happened and it stores it
in event-- in the parameter you pass. So in our case, it's a
event_win. Then when you have an update, you have to call begin
update and you pass the window where you want to start updating.
And in my previous videos, I was passing the event_win
into BeginUpdate and EndUpdate.
Well, as I was developing this,
the diff GUI code, I would
notice that when you would start to run the program and the mouse cursor
was outside of your window, the first event that you get,
the event window
that FindWindow finds is null. And so you would end up passing null
to BeginUpdate, and that would trigger
the "odd address" error in the debugger and
cause kind of undefined behavior.
And I think that that was actually the cause of a lot of those odd
address errors that I was getting in development
of Carl and I didn't realize it. So the fix for that is,
even though you can look
up event_win and it's often the same address as event.message,
sometimes it is null, and event.message
is always not null. So cast it to a WindowPtr
and pass that to BeginUpdate and EndUpdate,
and you should be good to go. So once we
get that out of the way, I was not getting any more errors and I
could proceed.
So I guess I'll walk through kind of how all this GUI code works.
This is all boilerplate, pretty much the same stuff that's in Carl.
I am looking up a pattern and using that to draw the
background of
the top where the two text boxes are.
And there's like a whole bunch of standard system
patterns that you can use, or you can draw your own and
store it in your resource file if you wanted. But there's a whole bunch
of system patterns that you
can reference by number, and they're documented in the Inside Macintosh
books or PDFs, and they are the
same ones that Mac Paint uses. So all of these these are
all in the same numerical order. So you can just
use one of these upper patterns and give
it like if you want to fill a rectangle, it will fill it with this
pattern. So that's pretty cool.
So anyway, back to the code,
so that looks up that pattern and stores it.
This looks up the tab width from our
resource file. So I didn't make a settings
window, but I just stuck it in the resource file.
And that way, since this resource file would be
in the resource fork of the application, you can just
open the application with ResEdit without the source code and
just go in here and change change
this tabwidth value from 4 to 8 or whatever you want, and
it would be reflected in the program. So that's
kind of cool, I thought.
It sets up the standard menus,
read the last-- or it initializes the window
basically just builds the main window. The two TextEdit controls from
and two, adds the diff button and
then set up the status bar rectangle.
Then read last paths,
looks in the resource fork and fetches the
two files that are stored there, which are the last.
So when you run diff, it stores the values of the from file or
folder and to file folder
and they're stored as strings and the resource fork, and so that's
just to avoid saving to like an external file that you'd have to carry around.
And then that way, every time you run it, it just has
them preserved. So a lot of
code there to basically just fetch it, convert it to a text like
a C string, stick it in the text file or in the TextEdit
control, then it runs update_diff_controls
which runs periodically and
just looks to see if the from
TextEdit box and the to TextEdit
box are have files or folders in them, and if
they are the go button-- the diff button, is enabled and
otherwise it's disabled, so you can't run a diff before you
select files. Because the
first time you run it is the last from and last to,
these won't be in the resource file or the resource
fork. So it just would start off with like labels
that say from and to and you have to pick a file. And then
this adjusts the scrollbar of the main TextEdit control
to the number of lines that are in the control.
So then we go into our main loop as
normal process all the regular things, dragging the window around or
quitting. And then if it gets an update or
a mouse down, it passes those through.
So diff_mouse_down
figures out where you clicked and if it's in the TextEdit control,
it passes that to TEClick.
If you click on the from TextEdit control,
it runs this askfileordirpath,
which is not a very good name, but it's a function
that I wrote and same for the to box.
And if you clicked the diff button,
it [sets] do_diff and then back in the
event loop, it checks for that and then runs
the actual diff, and it does that kind of indirectly
so that we can update after clicking the button.
Because once the diff function runs,
our GUI kind of is not responsive.
Then all this stuff is just boilerplate code for
if you click, if you move the scroll thumb
around or you click in the--
these like page up and down things, or you click the up or down arrows,
it scrolls the TextEdit control accordingly.
So that's pretty much all standard stuff. It's kind of
annoying how much boiler plate is needed. Like all of
this code is all to figure out how much
to adjust the TextEdit control that the scrollbar is attached to because
TextEdit controls don't have their own built in scrollbars.
So that's all pretty much standard.
When you run the diff, it
eventually, after looking everything up,
it just runs the same diffreg function that it
did before, except that previously
the diffreg code had a #define
diff_output, I think, to printf and so anytime it
would call diff_output,
it would actually be calling printf, which called THINK C's
printf. So I have defined my
own diff_output function, which acts like a printf,
and it basically just accumulates data
into a buffer and
then wait until it gets
a newline.
It converts that newline into a Mac style
newline and then calls diff_append_line and then shifts
that line out of the buffer.
diff_append_line expands tabs into spaces and
then insert that text into the TextEdit control.
And that's basically the gist of it.
The hard part came in all of this
code here, which diffs directories.
But I will first explain how this askfileordirpath works.
And normally you could just call SFGetFile
and then you would pass this as null and
you would get a reply.
And if a reply is good, and I love how Apple
names these things, like instead of successful or whatever,
it's-- the name of the member in the struct is good.
And just like if you close a window, it's not like close
or destroy, it's go away. Anyway.
So normally you just get a reply struct
reply.good is set, and then you have a reply.fName
and that's the file that the user selected in the dialog.
And then that's pretty simple.
And then this getpath
would run on it, which is another function I wrote that expands
the directory and volume reference that are passed
in that reply struct and basically just expands it into a fully
qualified path, which, excuse me, is the Mac
and HFS, I guess, are separated by colons instead of
slashes. So the full path would be like
Macintosh:folder:sub folder:file.
So it does all of this stuff,
and the way that it works is it looks up the directory that
the file is in, gets its name, prepends it to the
string, then has to recurse and ask
for the parent of that directory, and it keeps walking back until there
are no more parents, and each time it has to prepend its
folder into the beginning of the string. So it's
kind of annoying because we keep having to expand
the string and then because we have to reallocate
the string and then print the new
data at the beginning of the buffer because
we don't know the total length of it. I guess we could do this with
a large buffer and then just start writing from
the end of it. But it's
not a critical-- it's not like a function that runs very often.
So that's how that-- oh, so,
the hard part came in is, when you call SFGetFile and
you want to allow it to return a directory
or a folder. And the way that I did that is, you can hook
into-- you can get a callback each time the user clicks
on a control in that
standard dialog. So those would be like the list
of files, the drop down folders, the drive,
or they click on, like the Open button or whatever.
So the way that I hacked around that is, it calls
this call back,
and this callback is passed
the item code of what the user clicked on and then the dialog,
and helpfully you get this sfHookOpenFolder.
So the problem is you get this
hook the same if the user click double clicks
on a directory or highlights the directory and clicks Open.
So those are the two things that I actually needed to differentiate between.
The way that I am doing this is,
I am asking for the coordinates of the Open button
and getting the mouse cursor coordinates.
And if the mouse cursor coordinates are in
the rectangle of the Open button, then we know that the
user highlighted a directory and clicked Open.
And in that case, we just return getOpen
rather than this as the item code, because normally it would just return
whatever the hook passed. And so if
you double click on a folder, this doesn't pass and we
still return this, which recurses into
the subdirectory. So that's exactly what we wanted.
But it took me a long time to figure out how to do all that.
And so there you go.
So that all works.
What else?
I did have to write a stat function.
So this used to be xmalloc.c that had,
like, these little helpers in it, but eventually I was taking
so much code out of Carl's util.c
that this is my file now.
I wrote a stat
function that works the same
on Unix. It calls the HFS.
I don't know if that's specific to HFS, but the
File Manager function to get the
information of a category or catalog item,
which is a file or folder, and then we basically just
pass in the same data. So you get your creation
time, your modified time flags,
and then this is_dir can just check those flags to see if
it's a directory and then
the file size so that we can pass--
so that these stat structs that differed
relies on to print, like the modified
date and time of each file that
it's differing, that it can still
use the same data in construct. So we're
like trying to be compatible with diffreg here.
So that all works.
Pretty happy with where it's at right now.
So I guess we need to fix the bug where...
oh, and I just wanted to point out,
so if you look at the compiled application,
we should probably make an icon for it.
If you hit command+i,
you get this version string,
which is pretty neat and the way that that works--
because that is the same version string as
is shown and the About box.
So the way that that all works
is in the resource file,
there is a verss resource
which helpfully gives you this neat dialog and
you can set
all these things so you can fetch these from your application
and show them. But otherwise you can
just update this in the resource in ResEdit
each time you go to release a new version of the program.
So that's kind of cool.
And then obviously it also shows up
in Finder so you can get the version
information without even running the program. So that's
where everything is right now. I guess we will fix the bug of
the TextEdit control not clearing between diff runs,
then add a, uh, Edit menu. So I
guess that would be in... I have
this and it should work. I don't know why it's not
clearing that text control.
To insert text, you have to basically select...
So when you add text,
you have to do the SetSelect which puts the cursor at the very end
of the of the text set of control. So you
probably just need to do SetSelect zero and
then the length and then delete it.
So let's try that.
Let's try that, before we...
Alright.
And I think I'm going to release this-- do like
a zero day release on a BBS because, why not?
So if you want the code or the application,
you got to call my BBS.
You don't actually have to call. You can telnet or use the web interface.
But if you want to call, you can, if
you still have a modem.
Let's see if this works.
That's country code +1 312-654-0090.
And that should work up to 56K.
Okay, so I want to test one
file and then we'll test another one.
Alright. This should clear when
we do another one.
It does.
That's weird. It had that flash as it was selecting all of the data.
I wonder if I don't have to do that if there is a different way
to clear out all the data of a TextEdit control.
I know you can just directly manipulate
the text buffer underneath, but that seems
kind of not very nice.
Alright, I guess we'll just leave it as that select everything
and delete it.
Am I doing that in Carl?
Because when you view a different e-mail, it should
have to do the same thing. But I don't remember seeing that flash.
Oh, okay, maybe we can just do
TESetText and then zero then with nothing
in it, maybe that's nicer than selecting all
and deleting. Alright?
Cool. Okay, so we
fixed that bug, I guess.
And now we need to add an Edit menu so we
will open ResEdit.
[ crying in background ]
My son is crying for some reason.
Alright, so we're going to create an Edit menu
and then we need to add things to it and
we're not going to be able to Cut or Paste because
it's a read-only TextEdit control or it should
be anyway. So I guess we're just going to add Copy
and Select All, I guess.
So that's Copy and we'll give it a C.
I don't know why my son is crying.
Okay. I think that's all we need.
We just need to respond
to them, I guess, at the mouse-handling code.
Okay. I guess
we also have to disable them or disable
Edit when--
Okay, there's some other part of this we need to update.
Ah, yes, there is. We need to add
the ID of 130 to our menu bar.
So we will say Insert New Field(s) and 130.
Alright.
There, we got our Edit and Copy menus.
Okay, now I just need to respond to them
and disable them as needed.
So if you click on a menu
it would call inMenuBar
which we already have a
thing set up for.
Right. So I need to set up a
copy menu ID.
Sorry, I'm not paying attention here.
Not copy, edit. Sorry.
Edit menu ID.
And then I guess these are in order.
So 1 is Copy and 2 is Select All
So I think there's just a TECopy function that would just copy
whatever is selected in the text to control.
There is.
And if it's 2,
TESelText I think.
TESetSelect 0, 32767.
Yes, that place of doing
I don't know, what do you think is easier to understand?
32K like that or just...
It gets converted at compile time anyway, right?
Right? Yeah.
Okay.
I think that's all I needed for that.
We just need to be able to disable them
and stuff when there's
no text in the box, I guess.
Yeah.
Okay,
Let's do something simple here.
I should probably make
it when you click on a thing, the working directory
that it sticks you in is the directory that
you already picked. Because it's kind of confusing.
What am I doing? diff orig.
And we'll just select a bunch of text
and copy it, see if that worked.
I don't know why my son is freaking out.
No, it did not work.
Sorry.
Okay, let's that again.
What is LoWord(menu_id)...
Maybe these all share the same ID sequence...
So these are 3 and 4 or something? It doesn't seem right.
So isn't that 1?
Yes.
TECopy, that should just copy whatever is selected, right?
TECopy copies the selection range from the text specified
by the TextEdit handle into the TextEdit scrap.
Anything previously in the scrap is deleted.
Okay. What is the scrap? I just want the clipboard.
[ reading ]
Okay, why are there separate scraps?
That reminds me of a terrible Unix multiple
clipboard thing. Er, X11.
TEToScrap.
You must call the scrap manager function ZeroScrap to
initialize the desk scrap or clear its previous contents before
calling TEToScrap.
Okay, so ZeroScrap.
Alright, let's try this again.
Copy.
And then TeachText.
We don't have TeachText anymore?
There, so that worked. Cool.
Why didn't that redraw when we reactivated?
Weird.
Okay.
Does Select All work?
It does, cool.
And then you can copy that if you want.
I guess we could also make it like save the diff, but whatever.
And it would be nice to disable those when
there's no text in the box. So probably in
update_[diff_]controls we can do that.
Okay, so now if
there is any text...
Disable them. How do you disable a menu?
So I guess we should be storing the ID of those menus, right?
Because just like storing-- like a
constant was just doing them by numbers. It's not very
helpful to remember what they are.
So these do not get an ID.
I don't think-- like the actual
item in the menu.
Right? That's just the short.
That's only a preview menu.
Alright, so I guess we'll just have to store
them manually.
Okie doke.
And then we'll do this.
Then we'll need to disable them as well.
I know there's a way to mark code
so that you can...
Yeah.
How do you jump to a marker?
Nice. Option+click is the includes of the file and
command+click is your markers. That's very helpful.
Okay, so how do you disable
a menu item?
Highlight? No, not highlight.
Enable item, disable item. Cool. Okay, so...
DisableItem.
Oh, we need to fetch the menu as a handle.
Okay. I think
we already store menu handle somewhere.
Actually, we need to make that...
Are we even using this?
Okay.
I don't know why I was looking those up.
DrawMenuBar, I think just draws...
Right. You fetch the Apple menu that you create,
and then this adds all of the--
everything that's in the system to the menu.
So this does that all for you. So you do need the
Apple menu. The edit menu, we don't though, because...
edit_menu we'll use later.
Okay. And then-- ah, nice.
And then I guess we also need
to disable that when there's--
Right, so if there's no lines, select all.
Copy needs to be enabled or disabled based on whether
you've highlighted any text, not just whether there's any text in the box.
Let's see if that works just for Select All.
When we call TEClick, I guess we'll
have to look up whether there's a selection and then... yeah.
Alright. So Select All is disabled.
That's good. And if there's text,
it should be enabled.
Cool.
And then Copy has to be enabled or disabled based on
whether diff_te has a selection.
How do you find out if there's anything selected?
I'm not seeing how you actually find
out if there's a selection or not.
I guess you have to directly look in the
structure. That's kind of annoying.
selStart and selEnd
and see if it's zero.
Okay, I guess
we're doing the same thing with nLines anyway.
I hate if not else, it's like, backwards.
Then we would disable Copy.
Okay. And then I guess we can call that right after TEClick.
I guess that
makes sense because a function to get the selection doesn't
modify anything but setting a selection, it has to like highlight
the text and stuff. So you can just update the
cell, start and sel end and the structure.
Find it.
Okay. So Copy is not enabled.
And we can't type. That's good.
And now my
cursor-- oh, you're done.
It didn't update status.
It's weird. Okay,
so if there's no selection, you can't Copy.
But if you select something, you can copy. Awesome.
I guess we're done. Well, let me fix this.
Right. So if it's the same-- files are the same, otherwise say...
Should it say differences because it's a diff tool?
One file has differences between-- no.
One file has...
Files are the same.
Okie dokie.
Alright. And I guess we can bump
the version number because this isn't really
like a 0.1 application anymore. It's kind of like a 1.0
if you know what I mean.
So like I said, I'm going to release this,
so if you want to play around
with it or give me changes or something, that would be pretty cool.
It's under the same license as
the original BSD diff.
Cool, so I guess we're done here.
I should probably...
So I'm going to clear out those to and from saved values
because they're actually stored in the resource file.
And I guess we'll call this 1.0, man.
So we'll call this 1
0, final.
Short version string 1, and 1.0.
Alrighty. And then if we clear
out the saved-- oh, something
that I completely forgot to mention when you're recursing
through a directory, I didn't even show
how the code works. Sorry.
So when you select a file or folder
in from or to,
the code checks-- uses that isDir function
that I wrote that calls like a stat
on it, basically, and find out if it's a directory
or not. And then that way I can check that both of them are
either directories or files or folders or whatever.
But if you are diffing directories,
we recurse--
we get a list of files in the directory, and the way that it does that
is it opens--
gets the information for
the folder, then it
sets this FDirIndex, which tells it to get
the information for the first
file in the directory, folder.
I keep saying directory, but I guess they're called folders,
but some of the functions reference directories, I'm not really
sure. So it gets the information
for the first file and then the next time it would run, it would just
ask for the second file and so that it would just keep running until it
says that no such file with that index exists.
So it looks in the from directory and then for
each file that it finds it calls this file_should_be_diffed against
the file name. That function checks the file
extension of the file, which I know is not the same
thing on Mac OS with file
types and creators. But everything I think that we're going to be
diffing is like code. So the file type and creator I don't care about.
I care about the actual file type
and creator or sorry, the extension. So if the file is okay
to be diffed, it tries
to find that same file in the to folder,
and if they're present, it runs the diff. Otherwise it
skips it and says that the file is not found. So this
file_should_be_diffed functione does
some magic in that when
it initializes, it looks up this list of strings
in the resource file called FILE_EXTENSIONS.
And this is just a list of
strings that you can edit after the
program is released. Just like you can update the tab width, you
can put in whatever extensions you want in this list
through ResEdit, and it will consider
files that end with those extensions
as okay to be diff. And if it doesn't,
if the file extensions don't match, it won't diff them.
Because there's going to be a lot of like binaries and other crap that
don't make sense to be diffed. So that's another thing
that I put in there, so you can configure that however
you want without having to recompile the application.
So that's how that works.
And now I can clear
out the LAST_TO and LAST_FROM.
Then save it and then recompile the application.
We won't have an icon for the first version. But maybe somebody
that is good at making icons can make me an icon.
That would be a helpful contribution. I haven't even
looked into, like, do you have to make a tiny icon version
for, like, the list view?
I would imagine so.
Or maybe it auto scales. I don't know.
Anyway, let me just make sure that this runs without
those strings there. It should.
And then you just get the from and to strings and then the I
button is disabled. So this is how it would look the first
run. That's how we want it to look.
So I'll have to remember to clear those each time I make a release,
But we will build the application, save it as diff.
I don't know what Smart Link does, but go for it.
And then I guess we'll make a StuffIt
archive because that is how things
are done with the
binary and the source
and then maybe
store it-- converted into a
MacBinary, I guess.
Okay, so I
will make a new folder.
Why wouldn't you put the folder where I can see it?
Come on, man.
Then we'll make 1.0.
Then we will copy the binary there
and then put source.
And copy.
Alright. I hate how there's no
mass copy function. Like you can't copy a folder.
You can only duplicate it, which then renames it.
So we will duplicate.
And then make that our source directory?
There's no keyboard shortcut for delete.
Why isn't there?
Anyway. Source?
Cool. Cool.
And I guess that's it.
So I will make this a release and
make a new StuffIt archive from it.
With StuffIt Deluxe, I guess.
It's kind of weird how like this proprietary application like
WinZip, like just stayed around for
so long. Like, why didn't Apple make their own thing?
Because even Apple released things as StuffIt archives.
So we will make a new
archive and store it in...
call it diff 1.0.
Sure, why not?
Just in case someone is running this on an ancient version,
I guess. And we will Stuff diff and source.
Cool.
I wonder why that project file is so big.
Okie dokie, my friends. We have made a release.
And then, I guess,
like did people also-- because it was like
sit.hqx, which I guess is BinHex.
I guess you didn't really need this with a sit archive because
there's nothing in the data for the resource fork.
I just need to set the--
173K, seems pretty big.
A utility that I would like to have is just something to
quickly assign a type in a creator based
on the file extension. Because so
often when I download stuff from other sources
and eventually get it to my Mac, somehow it's lost
the type and the creator and I always have to go into
like MacTools and update them to like SIT!
or SITX or whatever to make them able
to be opened in StuffIt. And it's so annoying.
So what is a MacBinary then?
Well, I don't know. Let me know if you think
it's better to just release it as a .sit because you only
need StuffIt or a .hqx
because then you also need BinHex.
But why was that so big?
177K, what are you doing?
Close & Compact, maybe.
You know what?
I bet, is that storing the object?
Yeah, I think it is.
Yeah. So let's
clean the objects first.
You can compile it on your own man. You don't want to download my...
Alright, did that change anything? Does this update
automatically? There we go, 11K.
That is what I am talking about.
Let's do this again. Sorry, folks.
What's the difference between StuffIt Lite and StuffIt Deluxe?
We will ReStuff the source.
Okay, so for a release, I need to remember to remove
the objects, compact it,
and clear out to and from resources
out of the resource file.
Okay, now we can redo this.
77K, that is more reasonable.
Okay, now I will release it to the world.
SIT exclamation point.
And then, like, THINK C won't open
any text or .c or .h files that don't have a file type of TEXT.
It would be nice to have a utility where I can just quickly convert
everything in a directory or select a whole bunch of files at once.
Alright, we will release this now.
Let me connect to the old BBS.
This is just using my WiFi232, so it's
going over telnet and logging
me in automatically.
Okay, so if you want to download the
diff program or sources telnet, SSH, or
go through the web interface to Kluge - klud.ge.
Or you can connect through a dialup modem and you can
log in as guest if you don't want to make an account and I'm going
to put it in this Classic Mac folder,
And we
will send a file through XMODEM,
and I will send diff.1.0.sit.hqx.
Oh, man.
This is my childhood-- uploading and downloaded files over
a modem. It's fun. I guess
I could have connected at something faster than 38.4, but whatever.
Alright, well, I won't make you do the rest of this.
It'll be on my BBS if you want to download it,
feel free to make an account and upload your own code or
post on the forums or whatever.
Anyway, thanks for watching. And I guess
now that this program is done, we can get back to developing
Carl, so I'll see you in the next video.
Bye.