ThinkC [Study Group 1] Drawing on the Macintosh

Relating to ThinkC Development

eric

Administrator
Staff member
Sep 2, 2021
822
1,305
93
MN
scsi.blue
If you missed Study Group 0 dont worry! Come back after you've setup your environment here https://tinkerdifferent.com/threads...velopment-environment-setup-hello-world.1754/

Remember if you're not ready to move on to this week, don't worry, just come back when you are, we'll still be here to help!

Our goals for this week:
  • Read Chapter 3 in Macintosh C Programming Primer - Drawing on the Macintosh
    • Complete the examples Hello2, Mondran, ShowPICT, Screen Saver
  • Familiarize yourself with the QuickDraw Toolbox Manager
    • Besides the book, look through the QuickDraw Manager in Think Reference to get some ideas on other routines you can use
  • Take what you've learned and try to build a different screen saver or demo app!
  • Post here with questions, tips, and help each other out
Quickdraw seems to be one of the bigger managers in the toolbox, so take your time and it may take a few times to re-read to become familiar with the manager. Drawing, coordinates, and math needed are likely something you don't use daily so make sure to ask questions if you get stuck.

Since this is a bigger chapter and there's potential to build some cool things - Study Group 3 - Events will be in 2 weeks.
 
  • Like
Reactions: Patrick and Mu0n

BFEXTU

Tinkerer
Jul 15, 2022
156
142
43
It looks like this chapter involves Handles, Pointers and Type-Casting in C. If you are new to programming, you should take time to make sure you understand these concepts very well - go over them multiple times, since the programmatic syntax and meaning can be somewhat confusing. Also, misuse of Handles, Pointers and Type-Casting are 3 of the top reasons why applications crash (caused by accesses to unexpected or incorrect locations in memory). So, don't misuse them!!

By comparison, the drawing routines are pretty easy and always very fun.

So, for those who are interested, see the spoiler below. If you already know this stuff, then just skip it. ;) Otherwise, I hope it is helpful.

Simple Definitions:
Handle
: A handle is a "pointer to a pointer to something in memory." Or, at least that's what a long-haul trucker librarian once told me over a CB radio.

In C, writing*Handle dereferences a handle to get a Pointer

C code example:
myCMemPtr = *myCMemHdl; // dereference a CMemHdl to get a CMemPtr

...or to get all the way from a handle to an element of a structure:
surprise = (*myCMemHdl)->monkeySurprise; // the (* ) dereferences from the handle to the pointer and the -> dereferences from the pointer to the intended field

-- or --

surprise = (**myCMemHdl).monkeySurprise; // the ** dereferences from the handle to the structure and the . indexes to the intended field

n.b. You have to enclose any dereference using * with () so that the compiler knows to fully evaluate that expression before attempting to dereference to "->" or access "." the intended field/member in a data structure. (), -> and . are operators with the highest priority, and * is 1 step lower. So, if you didn't use (), there would be a compiler error.

For more info, see: Operator Precedence in C


Pointer: points to memory. Hey! Look at that! Well...huh! Who knew that monkeys could juggle flaming umbrellas! You don't see that every day! I'm glad I saw the ad!

In C, writing Ptr->someContainer dereferences a pointer to directly access a container in memory.

C code example:
myCMemPtr->monkeySurprise = 5000; // juggling monkeys is a surprise of high magnitude

(Similarly, for the sake of repetition, handles are merely a specific kind of pointer to a container that holds a pointer.)

Memory: A place to hold anything and everything a program needs to do its work

(Memory Warning: For software engineers, Memory, from the musical CATS, is the saddest of all songs. It represents how engineers feel as their lives and relationships begin slipping away after too much coffee and extreme, programming-related sleep deprivation. Some hapless souls may even turn into dreaded Code Vampires, also known as the Programming Undead. So, it's probably safest not to become a software engineer or listen to anything from CATS.)

C code examples:
myCMemory.monkeySurprise = 5000; // Note: when you are accessing a structure directly (no pointer or handle), you use a "." to get to the field.

Getting an effective address (pointer) to the structure or an element of a structure -- often when defined as a local variable in a routine:
myCMemPtr = &myCMemory; // get the effective address, a pointer, to the CMemory structure

myMonkeyPtr = &myCMemory.monkeySurprise; // get the effective address of the monkeySurprise field within the CMemory structure

As in my previous post, in C, you need to take the effective address (a pointer) of a structure/stack element when you want to pass it as a variable to another function.

Finally, in C, here is how you would define a memory structure in a .h file, along with its related pointer and handle.

Code:
struct Name {   // the main structure definition with all of its fields
   varType varName1;
   varType2 varName2;
   etc.
}
  typedef struct Name Name; // a typedef for the name of a struct
  typedef Name *Pointer; // define a pointer that points to it
  typedef Pointer *Handle; // define a handle that points to the pointer
...or to create a formal example from all of the information above:
Code:
struct CMemory {
   int numCats;
   int catCostumeType;
   int maxCoffee;
   int vampireTears;
   short monkeySurprise;
   Handle umbrellaHandle;
   Str255 codeZombiePSA;
};
typedef struct CMemory CMemory;
typedef CMemory *CMemPtr;
typedef CMemPtr *CMemHdl;
(n.b. full type definitions for structures and their pointers and handles can help prevent many accidental errors/misuse. Also, to evaluate the size of a variable or structure, you can use the macro sizeof(CMemory), for example.
Code:
#define kCMemSize sizeof(CMemory)  // create a constant for the size of a CMemory structure

You never have to count type sizes or bytes, just include the sizeof() macro in your code or a #define and the compiler will do it for you!

Why so much indirection?
Considering Handle->Pointer->MemoryContainer, the reason there is an extra layer of indirection is that handles are a friendly and consistent way for user software to keep track of dynamic structures in memory. Then, at the pointer level, the MacOS/System can manipulate pointers and blocks of memory to optimize memory handling (because it is very sneaky, and the task is complicated and much bigger than the scope of individual applications) without causing any problems at the user program level. If the system has to move something around in memory, it moves it and then tells the handle where it is supposed to point to find the container (or droids) it is looking for. And when an application dereferences from the handle to the new pointer, everything works -- like magic!

So...to repeat again, very generally:
HANDLE (flexible program reference) -> POINTER (direct access/system maintenance) -> MEMORY LOCATION (data structures, resources, etc.)

I'm Still Confused and Need a Bedtime Story:

"A Boy and His Socks"

(The best bedtime story ever by !Aesop)

Once upon a time, a boy named Rastenbury Phipps knew that he owned many socks, and he loved the synthetic feel of polyester. "How wonderful that I own so many polyester socks!" he exclaimed. However, his mother, Soxy Phipps, kept moving the socks around because she was an obsessive-compulsive sock-sorting fanatic who arranged the socks inside a giant, highly-organized Polyester Pagoda.

"She's definitely crazy," thought Rastenbury, while on his daily sock shopping and appreciation trip to Sock Emporium. Sometimes, on rainy days when Soxy was very bored, she would move the socks around multiple times and try to make them fit in the smallest possible space so that she had room for more socks! "What a fantastic way to spend my time," she mentioned to a pair of sassy, multi-colored argyles in drawer #3480224.

When Rastenbury wanted specific socks, all he had to do was ask Soxy where they were and she would immediately fetch them for him from the correct drawer in the Polyester Pagoda. She had eidetic sock memory and knew the location of every single pair! How miraculous!

One day, as Rastenbury absent-mindedly consumed warm milk and cookies, he concluded that it was enough just to remember that he had many different kinds of socks and that he knew how to find them. He trusted his mother and didn't ALWAYS need to know exactly where they were UNLESS he wanted to wear or admire them. In fact, sometimes, Rastenbury would look into the large hall mirror and quietly whisper "I am a sock admirer" and then look furtively to the left and right to make sure that nobody heard him. Apparently, the crazy apple didn't fall far from the applesock tree.

Regardless, until Rastenbury needed his socks and asked for a specific pair, his mother, Soxy, could sort them to her heart's content, and everyone lived happily ever after without ever experiencing sock errors or argyle aggression.

The Intentionally Repetitive Moral of the Story:
HANDLE Analogy
: Rastenbury knows that he has socks (somewhere) and knows how to find them -- by asking his mother, Soxy, for them
POINTER Analogy: Soxy knows exactly where the socks are, because she has placed them in specific drawers in the Polyester Pagoda
MEMORY CONTAINER Analogy: The specific drawer location in the Polyester Pagoda where Soxy has stored the socks

The End.

Advanced Memory End-Note:

If you are not afraid of additional toolbox technical detail, read the following section:

You might wonder why you can't just skip the handle dereference and use the pointer directly, and the answer is that you can...and may need or want to. So, in order to do that, you tell the system to move the chunk of memory high and out of the way, then you lock it, then you get the pointer, then you use it for whatever you want, and when you are done, you unlock it again so the system can go back to moving it around, as needed. The reason you have to lock it is that if the system happened to move the memory and its pointer out from under you while you were trying to use it, you would be using an old reference and either crash or trash memory. So, don't do that.

Or, in other words:
Code:
h = GetResource(rType,rID); // get some resource
MoveHHi(h); // move it high in the application (or system) heap
HLock(h); // lock it so the system knows not to try to relocate it
p = *h; / get the pointer to the resource

(do some pointer stuff that is safe because the memory is locked)

HUnlock(h); // unlock it when you are done, so the system knows that it is safe to move it around again

The reason you just don't lock a handle without first calling MoveHHi() is that you may cause heap fragmentation by locking a chunk of memory that prevents the system from using it as a large, contiguous block. MoveHHi() moves the related chunk as high as it can and out of the way.

Other Routines:
CompactMem() does a zone cleanup and compaction, purging all purgeable stuff and ReserveMem() reserves memory low in the current heap. Alternately, ReserveMemSys() reserves memory in the System Heap. Finally, FreeMem() checks the amount of free memory available and MaxMem() lets you maximize a zone and check how much memory is available -- you can look up all these routines in ThinkReference. The goal is always to maintain the largest contiguous block of memory, make sure there is enough memory before trying to do things, avoid fragmenting memory by locking things in the middle of the heap, and not creating "memory leaks" by allocating things and then not disposing of them when you are done. There are numerous ways to make your app very well-behaved with respect to memory allocation (NewPtr, NewHandle, etc.) using these routines, but it's something to continue to research for later. And, when allocating memory, the sizeof() macro is very handy to evaluate the size of a specific structures, as above.

Heap or Stack:
Heap:

When you explicitly allocate memory - via NewPtr, etc. - or load a resource or create a NewHandle, etc., it goes into the current HEAP. The heap is a large area of memory used by an application, or the system. It starts at the bottom of the related allocated address space and grows UPWARDS. For applications, the System creates it based on the SIZE resource info, visible in the Finder as min size, max size and preferred size.

Stack:
Conversely, when you define a local variable in an application routine, such as a structure or loop counter, it goes on the STACK. The stack starts at the top of the allocated address space and grows DOWNWARDS. When you pass local variables from routine to routine, all the pointers/memory references are relative to the current stack pointer. Because the stack grows downwards, everything you passed on the stack is a higher (or positive) relative reference and the calling parameters for each routine exist in a structure known as a StackFrame (for later discussion).

You never want to run out of space, take up too much space (that you don't really need), or have a Heap/Stack collision, where you allocate or use up all available memory (unbounded/excessive recursion, declaring large structures as local variables, etc.), and the stack and the heap run into each other (or you run out of stack space). So, code defensively and always make sure you have enough space, rather than just assuming that you do!

Code:
Example Application Memory Zone
=================
      STACK
=================
        |
        v

...and never the twain shall meet!  ;)

        ʌ
        |
=================
      HEAP
=================
 
Last edited:

OneGeekArmy

Tinkerer
Oct 31, 2021
87
225
33
Belgium
diskjockey.onegeekarmy.eu
It looks like this chapter involves Handles, Pointers and Type-Casting in C. If you are new to programming, you should take time to make sure you understand these concepts very well - go over them multiple times, since the programmatic syntax and meaning can be somewhat confusing. Also, misuse of Handles, Pointers and Type-Casting are 3 of the top reasons why applications crash (caused by accesses to unexpected or incorrect locations in memory). So, don't misuse them!!

By comparison, the drawing routines are pretty easy and always very fun.

So, for those who are interested, see the spoiler below. If you already know this stuff, then just skip it. ;) Otherwise, I hope it is helpful.

Simple Definitions:
Handle
: A handle is a "pointer to a pointer to something in memory." Or, at least that's what a long-haul trucker librarian once told me over a CB radio.

Writing *Handle dereferences a handle to get a Pointer

code example:
myCMemPtr = *myCMemHdl; // dereference a CMemHdl to get a CMemPtr

...or to get all the way from a handle to an element of a structure:
surprise = *myCMemHdl->monkeySurprise; // the * gets from the handle to the pointer and the -> gets from the pointer to the element

Pointer: points to memory. Hey! Look at that! Well...huh! Who knew that monkeys could juggle flaming umbrellas! You don't see that every day! I'm glad I saw the ad!

Writing Ptr->someContainer dereferences a pointer to directly access a container in memory.

code example:
myCMemPtr->monkeySurprise = 5000; // juggling monkeys is a surprise of high magnitude

(Similarly, for the sake of repetition, handles are merely a specific kind of pointer to a container that holds a pointer.)

Memory: A place to hold anything and everything a program needs to do its work

(Memory Warning: For software engineers, Memory, from the musical CATS, is the saddest of all songs. It represents how engineers feel as their lives and relationships begin slipping away after too much coffee and extreme, programming-related sleep deprivation. Some hapless souls may even turn into dreaded Code Vampires, also known as the Programming Undead. So, it's probably safest not to become a software engineer or listen to anything from CATS.)

code examples:
myCMemory.monkeySurprise = 5000; // ote: when you are accessing a structure directly (no pointer or handle), you use a "." to get to the field.

Getting an effective address (pointer) to the structure or an element of a structure -- often when defined as a local variable in a routine:
myCMemPtr = &myCMemory; // get the effective address, a pointer, to the CMemory structure

myMonkeyPtr = &myCMemory.monkeySurprise; // get the effective address of the monkeySurprise field within the CMemory structure

As in my previous post, in C, you need to take the effective address (a pointer) of a structure/stack element when you want to pass it as a variable to another function.

Finally, here is how you would define a memory structure in a .h file, along with its related pointer and handle.

Code:
struct Name {   // the main structure definition with all of its fields
   varType varName1;
   varType2 varName2;
   etc.
}
  typedef struct Name Name; // a typedef for the name of a struct
  typedef Name *Pointer; // define a pointer that points to it
  typedef Pointer *Handle; // define a handle that points to the pointer
...or to create a formal example from all of the information above:
Code:
struct CMemory {
   int numCats;
   int catCostumeType;
   int maxCoffee;
   int vampireTears;
   short monkeySurprise;
   Handle umbrellaHandle;
   Str255 codeZombiePSA;
};
typedef struct CMemory CMemory;
typedef CMemory *CMemPtr;
typedef CMemPtr *CMemHdl;
(n.b. full type definitions for structures and their pointers and handles can help prevent many accidental errors/misuse. Also, to evaluate the size of a variable or structure, you can use the macro sizeof(CMemory), for example.
Code:
#define kCMemSize sizeof(CMemory); // create a constant for the size of a CMemory structure

You never have to count type sizes or bytes, just include sizeof() and the compiler will do it for you!

Why so much indirection?
Considering Handle->Pointer->MemoryContainer, the reason there is an extra layer of indirection is that handles are a friendly and consistent way for user software to keep track of dynamic structures in memory. Then, at the pointer level, the MacOS/System can manipulate pointers and blocks of memory to optimize memory handling (because it is very sneaky, and the task is complicated and much bigger than the scope of individual applications) without causing any problems at the user level. If the system has to move something around in memory, it just tells the handle where it is supposed to point to find the container (or droids) it is looking for. And when an application dereferences from the handle, everything works -- like magic!

So...to repeat again, very generally:
HANDLE (flexible program reference) -> POINTER (direct access/system maintenance) -> MEMORY LOCATION (data structures, resources, etc.)

I'm Still Confused and Need a Bedtime Story:

"A Boy and His Socks"

(The best bedtime story ever by !Aesop)

Once upon a time, a boy named Rastenbury Phipps knew that he owned many socks, and he loved the synthetic feel of polyester. "How wonderful that I own so many polyester socks!" he exclaimed. However, his mother, Soxy Phipps, kept moving the socks around because she was an obsessive-compulsive sock-sorting fanatic who arranged the socks inside a giant, highly-organized Polyester Pagoda.

"She's definitely crazy," thought Rastenbury, while on his daily sock shopping and appreciation trip to Sock Emporium. Sometimes, on rainy days when Soxy was very bored, she would move the socks around multiple times and try to make them fit in the smallest possible space so that she had room for more socks! "What a fantastic way to spend my time," she mentioned to a pair of sassy, multi-colored argyles in drawer #3480224.

When Rastenbury wanted specific socks, all he had to do was ask Soxy where they were and she would immediately fetch them for him from the correct drawer in the Polyester Pagoda. She had eidetic sock memory and knew the location of every single pair! How miraculous!

One day, as Rastenbury absent-mindedly consumed warm milk and cookies, he concluded that it was enough just to remember that he had many different kinds of socks and that he knew how to find them. He trusted his mother and didn't ALWAYS need to know exactly where they were UNLESS he wanted to wear or admire them. In fact, sometimes, Rastenbury would look into the large hall mirror and quietly whisper "I am a sock admirer" and then look furtively to the left and right to make sure that nobody heard him. Apparently, the crazy apple didn't fall far from the applesock tree.

Regardless, until Rastenbury needed his socks and asked for a specific pair, his mother, Soxy, could sort them to her heart's content, and everyone lived happily ever after without ever experiencing sock errors or argyle aggression.

The Intentionally Repetitive Moral of the Story:
HANDLE Analogy
: Rastenbury knows that he has socks (somewhere) and knows how to find them -- by asking his mother, Soxy, for them
POINTER Analogy: Soxy knows exactly where the socks are, because she has placed them in specific drawers in the Polyester Pagoda
MEMORY CONTAINER Analogy: The specific drawer location in the Polyester Pagoda where Soxy has stored the socks

The End.

Advanced Memory End-Note:

If you are not afraid of additional toolbox technical detail, read the following section:

You might wonder why you can't just skip the handle dereference and use the pointer directly, and the answer is that you can...and may need or want to. So, in order to do that, you tell the system to move the chunk of memory high and out of the way, then you lock it, then you get the pointer, then you use it for whatever you want, and when you are done, you unlock it again so the system can go back to moving it around, as needed. The reason you have to lock it is that if the system happened to move the memory and its pointer out from under you while you were trying to use it, you would be using an old reference and either crash or trash memory. So, don't do that.

Or, in other words:
Code:
h = GetResource(rType,rID); // get some resource
MoveHHi(h); // move it high in the application (or system) heap
HLock(h); // lock it so the system knows not to try to relocate it
p = *h; / get the pointer to the resource

(do some pointer stuff that is safe because the memory is locked)

HUnlock(h); // unlock it when you are done, so the system knows that it is safe to move it around again

The reason you just don't lock a handle without first calling MoveHHi() is that you may cause heap fragmentation by locking a chunk of memory that prevents the system from using it as a large, contiguous block. MoveHHi() moves the related chunk as high as it can and out of the way.

Other Routines:
CompactMem() does a cleanup and compaction, purging all purgeable stuff and ReserveMem() reserves memory low in the heap. Finally, FreeMem() checks the amount of free memory available and MaxMem() lets you maximize a zone -- you can look up all these routines in ThinkReference. The goal is always to maintain the largest contiguous block of memory, make sure there is enough memory before trying to do things, avoid fragmenting memory by locking things in the middle, and not creating "memory leaks" by allocating things and then not disposing of them when you are done. There are numberous ways to make your app very well-behaved with respect to memory allocation (NewPtr, NewHandle, etc.) using these routines, but it's something to continue to research for later. And, when allocating memory, the sizeof() macro is very handy to evaluate the size of a specific structures, as above.

Heap or Stack:
Heap:

When you explicitly allocate memory - via NewPtr, etc. - or load a resource or create a NewHandle, etc., it goes into the current HEAP. The heap is a large area of memory used by an application, or the system. It starts at the bottom of the related allocated address space and grows UPWARDS. For applications, the System creates it based on the SIZE resource info, visible in the Finder as min size, max size and preferred size.

Stack:
Conversely, when you define a local variable in an application routine, such as a structure or loop counter, it goes on the STACK. The stack starts at the top of the allocated address space and grows DOWNWARDS. When you pass local variables from routine to routine, all the pointers/memory references are relative to the current stack pointer. Because the stack grows downwards, everything you passed on the stack is a higher (or positive) relative reference and the calling parameters for each routine exist in a structure known as a StackFrame (for later discussion).

You never want to run out of space, take up too much space (that you don't really need), or have a Heap/Stack collision, where you allocate or use up all available memory (unbounded/excessive recursion, declaring large structures as local variables, etc.), and the stack and the heap run into each other (or you run out of stack space). So, code defensively and always make sure you have enough space, rather than just assuming that you do!

Code:
Example Application Memory Zone
=================
      STACK
=================
        |
        v

...and never the twain shall meet!  ;)

        ʌ
        |
=================
      HEAP
=================
Please tell me you have a whole book in your future. This is an exceptionally great explanation. I laughed. I cried. I felt things. And $DEITY knows I haven't felt much since they removed AFP sharing support in macOS.
 
Last edited:

BFEXTU

Tinkerer
Jul 15, 2022
156
142
43
Thanks! Glad you enjoyed it! I just edited out a few more typos.

To your point, there are probably a number of books in my past that someone should have written, but owing to an existential temporal crisis, all the pages were suddenly eaten by interdimensional tapeworms before being written, or maybe after they were never published. So, I suppose it's possible they might spontaneously reappear in the future, especially if the tapeworms have indigestion. For context, interdimensional tapeworms resemble tiny dogs with very long tails...and the pages of the books may slightly resemble the homework of a 7th grader, except with more pictures. :D
 
  • Like
Reactions: OneGeekArmy

OneGeekArmy

Tinkerer
Oct 31, 2021
87
225
33
Belgium
diskjockey.onegeekarmy.eu
Thanks! Glad you enjoyed it! I just edited out a few more typos.

To your point, there are probably a number of books in my past that someone should have written, but owing to an existential temporal crisis, all the pages were suddenly eaten by interdimensional tapeworms before being written, or maybe after they were never published. So, I suppose it's possible they might spontaneously reappear in the future, especially if the tapeworms have indigestion. For context, interdimensional tapeworms resemble tiny dogs with very long tails...and the pages of the books may slightly resemble the homework of a 7th grader, except with more pictures. :D
Thing I never thought I would say #5413: Curse the interdimensional tapeworms! May they suffer an indigestion!
 
  • Haha
Reactions: BFEXTU

OneGeekArmy

Tinkerer
Oct 31, 2021
87
225
33
Belgium
diskjockey.onegeekarmy.eu
RE the topic at hand: on page 52 of the Primer, the author mentions the Big Long Window Technique and discourages its use.

If it has a name and is worth mentioning, there's probably a reason. Does anyone know what this technique was used for?
 
  • Like
Reactions: BFEXTU

jupo

New Tinkerer
Oct 31, 2021
3
10
3
Tucson, AZ, USA
It looks like this chapter involves Handles, Pointers and Type-Casting in C. If you are new to programming, you should take time to make sure you understand these concepts very well - go over them multiple times, since the programmatic syntax and meaning can be somewhat confusing.

Or if it's been so long since you've used C, you keep mixing up * and &.

I never learned it properly the first time (my C is all self-taught), and this chapter is the first time anyone explained *why* handles are a thing, so these types of explainers are appreciated. :)
 
  • Like
Reactions: BFEXTU

BFEXTU

Tinkerer
Jul 15, 2022
156
142
43
OneGeekArmy said:
Does anyone know what [Big Long Window Technique] was used for?

That is a great question with many subtleties lost to time, or so say certain interdimensional parasites!

Wide Windows (what I used to call them - but not an official moniker) or BLWT (which is like a BLT with more windowing) were a poor man's virtual desktop to try to give smaller-screened Macs @ 512x342 more real estate using a wide window. This strange practice may (or may not) have risen out of ultra-clandestine meetings of an early group of QuickDraw malcontents known as the The League of Horizontal Scrollbar Haters (LHSH), but don't quote me. There is no proof that such a group ever existed...and that's probably the way they wanted it. ;)

Wide Windows were not unlike modern-day Prezi, except that users had to manually drag the window left and right to use them. The GUI purists didn't like it for obvious reasons, namely:

1. WW replaced the well-defined horizontal scrollbar control and its related docProc (the documentProc) with a giant canvas. Ahhh! RUN! GIANT CANVAS!! VERY NAUGHTY!!

2. Offscreen application material that was relevant for the user experience/interaction wasn't visible, which was also true in the case of horizontally-scrolled windows, but horizontal scrollbars were easier to manipulate, just not according to the LHSH. It was theoretically OK to scroll data off the screen - as in text or spreadsheet documents, but not OK to hide the GUI. But, in the history of MacOS, there have been worse GUI nightmares than a wide window.

Hence it was frowned upon mightily...like this: :( - YEA, I frowne mightily upon thy endeavours, wyndow scoundrels!

There may have been a competing clandestine group of QuickDraw dissenters known as the League of Vertical Scrollbar Haters (LVSH) who advocated for Big & Tall Windows, but they didn't last very long (and everyone hated them anyway, even the LHSH, which is saying something). But, after getting all riled up, the LVSH quickly realized that there was no way to make a usable window that was taller than the screen height with menubar-only dragging, since there would be no way to move upwards beyond the top of the screen (unless windows supported dragging from the content region of the window... but they don't).

The "Upwards Dilemma," as it was eventually called, led this short-lived group to abandon its cause (and the pursuit of hi-tech altogether) and create a line of Big & Tall clothing stores with very big display windows. They sure showed us, boy howdy! Today, the echoing remnant of this silly splinter faction is that an "Upwards Dilemma" is used to describe any ill-conceived or ill-fated scenario where it is physically impossible to go any higher.

"There is no way to drag to 11." - Nigel Tufnel

[n.b. The restrictive window growing behavior I describe below specifically relates to the Finder, System 6.0.x and may also affect certain contemporary applications.]

Moving on, the Window Manager doesn't stop you from creating a window that is wider than the screen, but if a window already exists -- like a Finder folder window, for example, the Finder will not let you grow the window larger than the width of the main screen.

To see this Finder/WMgr constraint in action, do the following:
1. Create a window in the Finder under System 6.0.5 or System 7.1
2. Move it about 50% offscreen to the left
3. Grab the grow box in the lower right and try to make it wider
Result: The window will only grow as wide as the Window Manager's portRect. Poopie Head Window Manager!! So mean!!

BUT WAIT! There is an exception!

There is a special hotkey that I don't think was ever documented anywhere. IIRC, it is referred to as the "Ernie Key." I think it was named after the guy who implemented it, or maybe it had to do with a dark version of Sesame Street. Someone else may know. Anyway, if you hold down the command key and repeat the above experiment, you will see that it is possible to keep growing the window beyond the width of the Window Manager portRect or screenBits.bounds of the main screen. However, when you let go of the window growbox, you will see that the drag outline just snaps back to the max width of the main screen. Aww...you were so close!!

BUT WAIT! There is another exception!

The point of the Ernie Key was that it allowed users to create WW if they had multiple screens! So, while holding down the command key in the above test, if you can manage to release the growbox of the folder window after moving the mouse into the extended desktop of the 2nd screen, it should be possible to make a WW spanning 2 desktops...assuming I am remembering correctly. One main use case was related to power-users and very large spreadsheets -- letting Excel span 2 screens by expanding the Excel window with the command key, for example.

So, in essence, Apple vanquished the LHSH by saying: "Haha! We will add multiple screens and your reign of GUI abuse and terror will come to an end!" ...and so it did, and device-independent graphics support on the Mac rapidly evolved, along with its related software ecosystem.

Also with that evolution came the rise of 3rd party graphics companies (SuperMac, RasterOps, Radius) and the creation of true Virtual Desktops (instead of trying to cheat by making WW) that significantly extended the desktop size beyond the visible area. The monitor became a "window" into a much larger usable workspace. The only desktop size/depth constraints derived from available memory on the graphics cards and users could also use new features like Pan & Zoom to navigate -- no more scrolling OR dragging! And, beyond virtual desktops, as processor, OS and paging technology evolved, desktop swapping appeared, where users could create entire desktop setups and then just swap among them with a keypress (vs. scrolling or paging through groups of icons in a software UI). And, desktop swapping eventually began to include various OS emulators (linux, windows, etc.). Now, emulators run within a window. We are a nation of naughty sheep-shavers.

So, even though "Big Long Window Technique" was frowned upon and wasn't a great design choice (fictionalized LHSH-joking aside), it showed what could or should be possible and was incredibly insightful and predictive. From the earliest days of the Macintosh at 512x342, there was huge, pent-up demand and momentum from users who wanted more, could see it, and didn't want to wait. And, because of expansion-slotted machines, developer support ecosystem/personnel/tools, and 3rd-party innovation, users were able to get what they wanted within a few years.

Conceptually, from the standpoints of Design, Innovation and Entrepreneurship, whenever you see someone who is bending or extending the way things are currently being done, or trying to do something in a way that nobody expected, that unexpected input constitutes critical and invaluable Human Factors input and should act as a driver for change. It may also point the way towards the future and herald the birth of markets and companies.

As above, in the C Primer, the author says it was frowned upon, and he is (still) correct, but there was also more to the story. OGA's question points to what was missing, which was recognizing the incredible process of hardware/software innovation that was running at a furious pace by 1992. However, I don't fault the author, since an extended discussion was not really within the scope of an introductory chapter on QuickDraw, nor was a technical discussion about multi-screen window manipulation for power-user corner cases.

There will be many more Big Long Windows in the future of computer software, and they will point to areas where technology can (and probably will) evolve to the next level. So, watch for them!
 
Last edited:

Crutch

Tinkerer
Jul 10, 2022
292
226
43
Chicago
Moving on, the Window Manager doesn't stop you from creating a window that is wider than the screen, but if a window already exists, the Window Manager will not let you grow the window larger than the Window Manager portRect.

To see this WMgr constraint in action, do the following:
1. Create a window in the Finder
2. Move it about 50% offscreen to the left
3. Grab the grow box in the lower right and try to make it wider
Result: The window will only grow as wide as the Window Manager's portRect. Poopie Head Window Manager!! So mean!!
I don’t think this is quite right — _GrowWindow take a sizeRect as a parameter that lets applications pass in arbitrary min and max sizes for the drag outline. Historically most apps (as Inside Macintosh recommended) passed in screenBits.bounds or something similar for the max size, resulting in the behavior you describe. I am pretty sure nothing in the _GrowWindow trap actually restricts the size of the rect to the portRect of the WMgrPort (why would it?). I bet you a nickel that if you call _GrowWindow with a sizeRect of {0, 0, 32767, 32767} it will let you keep dragging that grow box wider and wider.

But apologies, none of this matters for the exercise of the week!
 

BFEXTU

Tinkerer
Jul 15, 2022
156
142
43
I will set up a 2-screen system and try it with the command hot-key in the Finder to see if it matches what I remember. The Finder definitely restricts grow to the width of the main screen, even if you override it with the command key (it snaps back)...and the command key is definitely the override I remember. Anyway, the last time I tried anything like this was in the early '90s. But, I will check it. I know the Finder does it, but the behavior may be different at the app level - like with Excel, but I can probably check v4.0. Maybe Excel grows without the command key trick.
 

BFEXTU

Tinkerer
Jul 15, 2022
156
142
43
I don’t think this is quite right — _GrowWindow take a sizeRect as a parameter that lets applications pass in arbitrary min and max sizes for the drag outline. Historically most apps (as Inside Macintosh recommended) passed in screenBits.bounds or something similar for the max size, resulting in the behavior you describe. I am pretty sure nothing in the _GrowWindow trap actually restricts the size of the rect to the portRect of the WMgrPort (why would it?). I bet you a nickel that if you call _GrowWindow with a sizeRect of {0, 0, 32767, 32767} it will let you keep dragging that grow box wider and wider.

But apologies, none of this matters for the exercise of the week!

I set up a 2-screen system in a Mac II, including a Spectrum/8 Series III in Slot $9 and a Thunder/24 in Slot $E, booting with a System 6.0.5 disk on FloppyEmu to verify the info from my original post above, which was a good 30-year trivia test!

With respect to the way the Finder behaves and draws windows, I passed the test and remembered correctly!

Specifically:
1. The Finder constrains/restricts max folder window width to the width of the primary screen, even when a 2-screen extended desktop is available.
2. The Ernie Key (command hotkey) + dragging the window grow box from the main to the 2nd desktop (to the right in my case) allows the user to expand Finder windows beyond the width of the primary screen (and will create a very wide, 2-screen window).
3. If you adjust the window size of a very wide 2-screen window after that without using the hotkey, the window will auto-snap back to the primary screen width/location.
4. With 1 screen present, using the hotkey, the Finder will let you drag a wide window outline (assuming you moved the window far to the left to allow for the grow test), but won't actually expand the window beyond the screen width. When you let go of the mouse, the grow outline just snaps back to the primary (only) screen width.

I tried TeachText and it let me expand to the 2nd screen without the hotkey. And, per the remark from @Crutch, I agree that there should be no limit, but the Finder definitely constrains windows in System 6.0.x and requires a hotkey to make them bigger. I didn't test Excel under 6.0.5, but I think I remember that it had a similar issue.

So, then, I tried System 7.1. It doesn't have idiosyncrasies #1-3 above, but does have #4. However, when multiple screens are present, by defaut, it is permissive when growing windows instead of restrictive. So, the behavior I describe above was changed or fixed at some point with the appearance of System 7+, or at least by System 7.1, as graphics evolved on the Mac. Under 7.1 Excel 4.0 let me create big windows without using the command key while dragging.

I hope this info helps put the mystery to rest. I will update my original post to mention that the behavior is tied to System 6.0.x and specifically occurs in the Finder, at least, if not elsewhere. (done)
 
Last edited:

Crutch

Tinkerer
Jul 10, 2022
292
226
43
Chicago
Yes, I agree the Finder does that - I am just clarifying it’s not a Window Manager restriction per se, the Finder is simply telling the Window Manager to do that. In one’s own application you can make the sizeRect as big as you like! (And the issue is caused by the Finder [or other apps] passing &qd.screenBits.bounds to _GrowWindow, not the WMgrPort‘s portRect, though those rectangles happen to be identical on single-monitor Macs.)

Thanks for the discussion.
 
Last edited:
  • Like
Reactions: BFEXTU

Crutch

Tinkerer
Jul 10, 2022
292
226
43
Chicago
C code example:
myCMemPtr = *myCMemHdl; // dereference a CMemHdl to get a CMemPtr

...or to get all the way from a handle to an element of a structure:
surprise = *myCMemHdl->monkeySurprise; // the * gets from the handle to the pointer and the -> gets from the pointer to the element

One more small thing for those following along: the last line here actually doesn’t compile. (“Value has no members” error in THINK C.)

“->” has precedence over “*” so to get the desired element you need to force the dereference to occur before the struct access, which you can do with

C:
surprise = (*myCMemHdl)->monkeySurprise

or in my opinion the clearer

C:
surprise = (**myCMemHdl).monkeySurprise

where this latter form stresses that you are accessing a handle and reminds one to contemplate whether HLock() etc. is necessary every time.
 

BFEXTU

Tinkerer
Jul 15, 2022
156
142
43
Yes, I agree the Finder does that - I am just clarifying it’s not a Window Manager restriction per se, the Finder is simply telling the Window Manager to do that. In one’s own application you can make the sizeRect as big as you like! (And the issue is caused by the Finder [or other apps] passing &qd.screenBits.bounds to _GrowWindow, not the WMgrPort‘s portRect, though those rectangles happen to be identical on single-monitor Macs.)

Thanks for the discussion.
The change I made was to clarify that the Finder is doing the restricting. I also think that there were other apps that followed the same restrictive vs. permissive model. And, to the point of that post, stretching windows beyond the width of their screens was originally frowned upon on single-screen Macs. I haven't specifically looked at those toolbox calls in a long time, but there is something else nagging at me - maybe I will remember later. The default behavior may have inherited from original sample code for QuickDraw/window management...or maybe there was something about ceding control to the OS/Toolbox for managing updates - SizeWindow() maybe. I guess I'll look over IM and write some window code to see If I can recall any other legacy issues.
 

Crutch

Tinkerer
Jul 10, 2022
292
226
43
Chicago
Right, like I said many apps just passed screenBits.bounds to GrowWindow as the sizeRect. But that was an app’s choice, nothing in the Toolbox prevented making windows extend all the way to MAXINT. (It was admittedly really useless to do so in the days before multiple screens!)
 

BFEXTU

Tinkerer
Jul 15, 2022
156
142
43
One more small thing for those following along: the last line here actually doesn’t compile. (“Value has no members” error in THINK C.)

“->” has precedence over “*” so to get the desired element you need to force the dereference to occur before the struct access, which you can do with

C:
surprise = (*myCMemHdl)->monkeySurprise

or in my opinion the clearer

C:
surprise = (**myCMemHdl).monkeySurprise

where this latter form stresses that you are accessing a handle and reminds one to contemplate whether HLock() etc. is necessary every time.
Yes - somewhat dusty on my part. I haven't programmed in C for a long time. When I was writing the post, I almost remembered the parentheses, but I ignored that squeaking syntactic urge. :) Thanks - I will fix them. But, maybe a helpful error for this thread on learning how to program in C! :D

The reason you might want to use one form of dereferencing or the other, is definitely for clarity, as Crutch says, and it may depend on what kind of type-casting you are doing. You will end up using both.

When you are casting a handle dereference to a specific pointer type, using "->" on the right side may be easier to read. In general, "->" when using pointers is probably best vs. (*ptr).field syntax.

Handles always require some use of (*) or (**) depending on any related type-casting. You will be casting everything at some point in the code hierarchy, whether at allocation time or after the fact. Classic examples are calling GetResource() or NewPtr() and casting the returned handle or pointer to the specific type you need for your data structure. Another common use for casting is when you pass a parameter to a subroutine that has specific type requirements where the type heterogeneity is acceptable. Trouble sometimes arises when you try to cast between types that are not compatible, but hopefully, the compiler warns you (but not always).

If you are locking a handle before using it, there is no reason to keep double-dereferencing it (**) when accessing many fields. So, you could just dereference to get the pointer and use "->".

The main problem with casting into multi-leveled references or accesses (i.e. a handle to a pointer to a structure to a handle to ...) can be that you end up with lots of parenthetical nesting on the left side that is visually hard to parse. Managing/casting resources is pretty easy; managing/casting anything relating to Data I/O can become much more complicated.

Otherwise, as I mentioned in one of my earlier posts, casting really deserves its own extended discussion. For new programmers, it can be another confusing head-banger, like learning about handles, pointers, dereferencing and data structures.
 
Last edited:

BFEXTU

Tinkerer
Jul 15, 2022
156
142
43
Right, like I said many apps just passed screenBits.bounds to GrowWindow as the sizeRect. But that was an app’s choice, nothing in the Toolbox prevented making windows extend all the way to MAXINT. (It was admittedly really useless to do so in the days before multiple screens!)
Yeah...but there was something more than that - like default behavior when passing false to SizeWindow()? It's nagging at me, but I just don't remember. I will just have to try it. Later, I guess.