AuntieDialog.html

<HTML>
<HEAD>
   <TITLE>AuntieDialog</TITLE>
</HEAD>
<BODY BGCOLOR="#FFFFFF">
<P><TABLE BORDER=0 CELLSPACING=0 CELLPADDING=10 WIDTH=450>
   <TR>
      <TD>
         <H1>AuntieDialog</H1>
         
         <P>Pete Gontier<BR>
         Apple Carbon High Level Toolbox Engineering<BR>
         &copy; 1999-2000 Apple Computer, Inc.</P>
         
         <H2>
         
         <HR>
         
         <BR>
         Introduction</H2>
         
         <P>AuntieDialog is a module, provided in source code form,
         which replaces some key Dialog Manager calls with calls to
         Control Manager and Window Manager, thus freeing you from
         struggling with the Dialog Manager API, yet allowing you to
         use the familiar tools you've always used to build
         dialogs.</P>
         
         <H2>But Why?</H2>
         
         <P>For several squillion years, developers have chafed at
         the limitations of the Dialog Manager. In fact, there's an
         entire Technote about how badly the Dialog Manager sucks.
         The Technote suggests that rather than attempt to bludgeon
         the Dialog Manager into working the way you need it to, you
         should use Window Manager and Control Manager. That's a fine
         suggestion, except that popular dialog creation tools assume
         you'll be using the Dialog Manager to manage your dialogs,
         and there are no Human Interface Toolbox (HIT) calls outside
         of Dialog Manager which read dialog resources.</P>
         
         <H2>Recommended Uses</H2>
         
         <P>AuntieDialog is best suited for use by new code, although
         in the long run you might be better off investing the time
         in getting rid of your existing Dialog Manager code as well.
         (For you conspiracy theorists out there, this does not mean
         Apple is considering abolishing the Dialog Manager, as much
         as we might like to.) Another thing AuntieDialog does not
         imply is that you should abandon the use of such high-level
         calls as <CODE>StandardAlert</CODE>. Dialog Manager is
         perfect for use by such high-level calls, which present a
         simple user interface. Dialog Manager's limitations really
         begin to show when you try to build a more complicated user
         interface on top of it.</P>
         
         <H2>Limitations</H2>
         
         <P>AuntieDialog is not a Dialog Manager replacement.
         Remember, Dialog Manager sucks. There'd be no point in
         slavishly emulating it. Instead, AuntieDialog offers more
         flexibility and fewer limitations, but you're not going to
         be able to drop this code into your project and blow away
         all instances of <CODE>#include &lt;Dialogs.h&gt;</CODE>
         without doing some work.</P>
         
         <P>For example, AuntieDialog provides no way to designate a
         window as system-modal. (In fact, there's never been a way
         to make an arbitrary window system-modal; the fact that
         AuntieDialog cannot be used with <CODE>ModalDialog</CODE>
         just makes the problem more evident.) In other words, you
         can't post a window which prevents the user from switching
         to another application. You can look at this as a limitation
         of AuntieDialog or you can see it as an opportunity to
         provide a better user experience for your users. We think
         system-modal windows are almost never in the user's best
         interest. And if you do find an exceptional case worthy of a
         system-modal window, it's likely to be very simple, so
         AuntieDialog is probably not called for anyway &emdash; just
         use Dialog Manager for such dialogs.</P>
         
         <H2>Compatibility</H2>
         
         <P>You need to query Gestalt to make sure Appearance is
         present before calling AuntieDialog. Consult the DTS Q&amp;A
         "Appearance Versions" for more info, and of course do your
         own testing of any program which incorporates this code. At
         present, AuntieDialog requires CarbonLib, which implies
         Appearance, but AuntieDialog may some day support
         InterfaceLib apps.</P>
         
         <H2>Data Types</H2>
         
         <P>There are no new data types in AuntieDialog. Isn't that a
         relief? I've designed this thing to stay out of your way and
         let you use the Window Manager and Control Manager to the
         fullest extent allowed by law. There are some function
         pointer types, but I'm feeling like a lawyer this evening,
         so I'm going to claim these are not data types.</P>
         
         <H2>Resources</H2>
         
         <P>I think you'll see a pattern developing here: there are
         no new resource formats in AuntieDialog. Here's how
         AuntieDialog treats the Dialog Manager resources:</P>
         
         <P><TABLE BORDER=0 CELLSPACING=0 CELLPADDING=10>
            <TR>
               <TD>
                  <P ALIGN=center><B>resource type</B></P>
               </TD>
               <TD>
                  <P ALIGN=center><B>what is it?</B></P>
               </TD>
               <TD>
                  <P><B>AuntieDialog behavior</B></P>
               </TD>
            </TR>
            <TR>
               <TD COLSPAN=3>
                  <P>
                  
                  <HR>
                  
                  </P>
               </TD>
            </TR>
            <TR>
               <TD>
                  <P ALIGN=center><CODE>DLOG</CODE></P>
               </TD>
               <TD>
                  <P ALIGN=center>dialog template</P>
               </TD>
               <TD>
                  <P>fully supported in nearly the same way as Dialog
                  Manager</P>
               </TD>
            </TR>
            <TR>
               <TD>
                  <P ALIGN=center><CODE>DITL</CODE></P>
               </TD>
               <TD>
                  <P ALIGN=center>dialog item list</P>
               </TD>
               <TD>
                  <P>fully supported in nearly the same way as Dialog
                  Manager</P>
               </TD>
            </TR>
            <TR>
               <TD>
                  <P ALIGN=center><CODE>CNTL</CODE></P>
               </TD>
               <TD>
                  <P ALIGN=center>control template</P>
               </TD>
               <TD>
                  <P>fully supported in nearly the same way as Dialog
                  Manager</P>
               </TD>
            </TR>
            <TR>
               <TD>
                  <P ALIGN=center><CODE>dftb</CODE></P>
               </TD>
               <TD>
                  <P ALIGN=center>new-style<BR>
                  dialog item color and text style table</P>
               </TD>
               <TD>
                  <P>fully supported in nearly the same way as Dialog
                  Manager</P>
               </TD>
            </TR>
            <TR>
               <TD COLSPAN=3>
                  <P>
                  
                  <HR>
                  
                  </P>
               </TD>
            </TR>
            <TR>
               <TD>
                  <P ALIGN=center><CODE>ictb</CODE></P>
               </TD>
               <TD>
                  <P ALIGN=center>old-style<BR>
                  dialog item color and text style table</P>
               </TD>
               <TD>
                  <P>ignored in favor of <CODE>dftb</CODE> to
                  maximize Appearnce-savviness and minimize
                  implementation complexity</P>
               </TD>
            </TR>
            <TR>
               <TD>
                  <P ALIGN=center><CODE>dctb</CODE></P>
               </TD>
               <TD>
                  <P ALIGN=center>dialog color table</P>
               </TD>
               <TD>
                  <P>ignored in favor of maximizing
                  Appearance-savviness</P>
               </TD>
            </TR>
            <TR>
               <TD>
                  <P ALIGN=center><CODE>dlgx</CODE></P>
               </TD>
               <TD>
                  <P ALIGN=center>dialog template extension</P>
               </TD>
               <TD>
                  <P>ignored in favor of maximizing
                  Appearance-savviness</P>
               </TD>
            </TR>
            <TR>
               <TD>
                  <P ALIGN=center><CODE>ALRT</CODE></P>
               </TD>
               <TD>
                  <P ALIGN=center>alert template</P>
               </TD>
               <TD>
                  <P>not supported; AuntieDialog does not make the
                  distinction between alerts and dialogs</P>
               </TD>
            </TR>
            <TR>
               <TD>
                  <P ALIGN=center><CODE>alrx</CODE></P>
               </TD>
               <TD>
                  <P ALIGN=center>alert template extension</P>
               </TD>
               <TD>
                  <P>not supported; AuntieDialog does not make the
                  distinction between alerts and dialogs</P>
               </TD>
            </TR>
            <TR>
               <TD>
                  <P ALIGN=center><CODE>actb</CODE></P>
               </TD>
               <TD>
                  <P ALIGN=center>alert item color and text style
                  table</P>
               </TD>
               <TD>
                  <P>not supported; AuntieDialog does not make the
                  distinction between alerts and dialogs</P>
               </TD>
            </TR>
         </TABLE>
         <BR>
         </P>
         
         <P>One other thing regarding resources bears repeating and
         clarification: the policy of the HIT with respect to
         releasing resources has always been that all resources are
         assumed purgeable and are never released (though it's safe
         for an HIT client to explicitly release resources as soon as
         that client is certain HIT no longer needs those resources).
         Although we can make some pretty good guesses at the
         reasoning behind this policy, it's difficult to explain
         conclusively because there's not a lot of documentation
         about the smaller design decisions made during the
         development of the original Macintosh. In any case,
         AuntieDialog adopts the same policy in order to avoid
         creating any additional confusion during your debugging
         sessions.</P>
         
         <H2>AuntieDialog and Control IDs</H2>
         
         <P>Understanding how AuntieDialog uses control IDs is
         critical. This usage is designed to be as unobtrusive as
         possible while at the same time enabling you to refer to
         controls with index values like you would refer to dialog
         items. If you would like to use other values, you need to be
         aware of the facilities AuntieDialog provides so you can
         make an informed decision as to how to choose those
         values.</P>
         
         <H3>The Default Behavior and the Values it Produces</H3>
         
         <P>When AuntieDialog appends a list of controls to a window,
         it assigns each control an ID immediately after creating the
         control. The signature of the ID is kAuntieDialogSignature
         ('aunt', a value in the Apple-only range).</P>
         
         <P>To choose 'id' field of the ID, AuntieDialog counts the
         controls which are already in the window (with some
         exceptions we'll get to in a moment). The first new control
         gets the count + 1, the second control gets the count + 2,
         and so on. So, when you add controls to a window which has
         none, the first control will have ID 1, the second will have
         ID 2, and so on. If you add controls to a window which
         already has three controls, the first control will have ID
         4, the second 5, and so on.</P>
         
         <P>Any control whose ID signature is not
         kAuntieDialogSignature does not contribute to the count of
         controls in a window. You might wonder where such controls
         would come from, since AuntieDialog always sets the ID
         signature of the controls it creates to
         kAuntieDialogSignature. First, the root control does not
         appear in any dialog item list, is created by AuntieDialog
         automagically, but is not given kAuntieDialogSignature.
         Second, controls which are created by other controls, for
         example the scroll bars created by a list box control, have
         no ID signature. Since for obscure reasons AuntieDialog
         cannot predict where in the control hierarchy these controls
         will appear, it makes no attempt to assign them ID
         signatures, which is probably what you want anyway.</P>
         
         <P><TABLE BORDER=1 BGCOLOR="#FFFFCC" CELLSPACING=0 CELLPADDING=10>
            <TR>
               <TD>
                  <P><B>Note:</B> You can expect standard system
                  controls created by other controls to have no ID
                  signature, but you may encounter custom controls
                  which have other behavior. You may need to set the
                  ID signature of these controls to 0 yourself. If it
                  isn't possible to change the ID signature for such
                  a control, that control is probably not compatible
                  with AuntieDialog.</P>
               </TD>
            </TR>
         </TABLE>
         <BR>
         </P>
         
         <P>AuntieDialog's default control ID is exactly the behavior
         you want if you want ID values which correspond to dialog
         item index values. With a window full of controls whose IDs
         conform to the default behavior of AuntieDialog, you can use
         the function <CODE>GetControlByID</CODE> to get a
         <CODE>ControlRef</CODE> in much the same manner as you would
         use <CODE>GetDialogItemAsControl</CODE>.</P>
         
         <P>But you must accept one more restriction in order to make
         this work: If you plan to dispose some controls and append
         others &emdash;&nbsp;say, when the user clicks a non-current
         tab in a tabs control &emdash; you should only dispose
         controls whose IDs are at the end of the range of values for
         controls in a given window. This ensures the controls you
         later append will acquire a range of IDs which is contiguous
         with the controls which remain after the deletion. The
         Dialog Manager doesn't allow you to delete items from the
         middle of the list, but Control Manager gives you the
         freedom to dispose any control at any time, so you need to
         be aware of the effect it will have on AuntieDialog.
         Deleting controls whose ID is not at the end of the range
         for a given window creates the possibility of duplicate IDs,
         which would make using <CODE>GetControlByReference</CODE>
         somewhat less than enjoyable. You need to delete controls in
         much the same way as you would shorten a dialog item list
         with ShortenDITL.</P>
         
         <P><TABLE BORDER=1 BGCOLOR="#FFFFCC" CELLSPACING=0 CELLPADDING=10>
            <TR>
               <TD>
                  <P><B>Sidebar:</B> There are some very credible
                  people, one of whom owns the Control Manager as of
                  this writing, who believe deleting and recreating
                  controls is an inherently risky endeavor. One
                  problem scenario is when a user clicks a
                  non-current tab in a tabs control under adverse
                  conditions such as low memory. If the controls for
                  the tab which is becoming current cannot be
                  created, the operation will fail; the obvious
                  fall-back position is to recreate the controls for
                  the original tab. Unfortunately, this operation may
                  also fail. Now the fallback position is to close
                  the window and apologize to the user. However, at
                  this point we've involved a lot of code to handle
                  exceptional cases, and as most experienced software
                  engineers know, this kind of code tends to get the
                  least testing and is most likely to blow up. The
                  people who concern themselves with this issue think
                  it's much better to create all the controls you'll
                  ever need as you bring up a window and hide and
                  show them as needed. Nothing in AuntieDialog
                  prevents or discourages you from using this
                  technique. And from the system's perspective, this
                  is a non-issue; the system doesn't care why you are
                  deleting and creating controls; the responsibility
                  for pondering this matter falls squarely into your
                  lap.</P>
               </TD>
            </TR>
         </TABLE>
         </P>
         
         <H3>How to Override the Default Behavior</H3>
         
         <P>OK, the default behavior is all well and good, but
         suppose you're ready to go beyond simulated dialog item list
         indexes. If you want control IDs to follow some other logic
         (say, pointers to instances of a C++ class), you should
         probably specify a non-NIL control creation function:</P>
         
         <PRE>typedef pascal
OSStatus (*ControlCreationProcPtr) (ControlRef);</PRE>
         
         <P>You write a function of the above type, and it is called
         from inside <CODE>AppendDialogItemsAsControls</CODE> after
         each control is created. (The control's ID has already been
         assigned according to the default behavior described above.)
         Your function decides whether and how to change the control,
         including disposing it, based on the control itself and
         whatever other criteria you choose.</P>
         
         <P>You can, of course, change the ID of any control (using
         <CODE>SetControlID</CODE>), including those created by
         AuntieDialog, but you will probably want to make sure the
         value is unique with respect to a given window if you want
         <CODE>GetControlByID</CODE> to continue working well.</P>
         
         <P>Note that addresses of heap blocks (including those
         produced by <CODE>NewPtr</CODE>, <CODE>NewHandle</CODE>,
         <CODE>malloc</CODE>, and <CODE>operator new</CODE>) are
         unique even beyond the context of a given window, so if you
         want to associate the address of a heap block (say, a C++
         object pointer) with a control, that address will be
         suitable for use as a search key for
         <CODE>GetControlByID</CODE>. Be sure, though, that all
         controls in a given window have IDs which are unique with
         respect to each other. For example, don't expect a C++
         object pointer to be a useful control ID if the rest of the
         control IDs for the controls in the given window are still
         dialog item list indexes. (Not that a C++ object pointer
         would ever be as low as a dialog item list index, but you
         should still be careful.)</P>
         
         <P>This callback function should not allow C++ exceptions to
         propagate.</P>
         
         <H2>Functions</H2>
         
         <P>The AuntieDialog package contains functions for creating
         a window full of controls, for appending lists of controls,
         for removing child controls, for counting the controls in a
         window, and for handling events in such a window. The rest
         is up to you and Window Manager and Control Manager.</P>
         
         <H3>CountControlsInWindow</H3>
         
         <PRE>OSStatus CountControlsInWindow (WindowRef, UInt16 *);<BR></PRE>
         
         <P>This function is roughly analagous to
         <CODE>CountDITL</CODE>. It counts the number of controls in
         a window, excluding the root control and any controls whose
         ID signature is not kAuntieDialogSignature. If the window
         has no root control, this function fails with an error.
         Controls with ID signatures other than
         kAuntieDialogSignature are skipped under the assumption that
         they are created by other controls, such as the scroll bar
         controls created by the list box control, and not by
         AuntieDialog. (Keep this is mind if you assign your own ID
         values.) You may find this function useful if you adopt the
         AuntieDialog convention of storing dialog item list indexes
         as control ID values. If you need to know the ID of the
         first control which would be added by calling
         <CODE>AppendDialogItemsAsControls</CODE>, call
         <CODE>CountControlsInWindow</CODE> and add one to the
         result.</P>
         
         <H3>AppendDialogItemsAsControls</H3>
         
         <PRE>OSStatus AppendDialogItemsAsControls
   (short resID, WindowRef, ControlCreationProcPtr);<BR></PRE>
         
         <P>This function is roughly analgous to
         <CODE>AppendDialogItemList</CODE>. The chief difference, of
         course, is that this function creates controls, not dialog
         items. Another important difference is that this function
         makes no attempt to place the new controls anywhere in
         particular within the window or resize the window to fit the
         new controls. The dialog item list template whose ID you
         specify contains the final location of the controls, and
         they must fit in the window if you want them to be visible
         to the user.</P>
         
         <P><CODE>AppendDialogItemsAsControls</CODE> is called by
         <CODE>NewAuntieDialog</CODE>.</P>
         
         <P><CODE>AppendDialogItemsAsControls</CODE> inhibits control
         drawing and invalidates control regions if the parent window
         is visible to prevent some controls from drawing before
         others. So, if you have a window for which you must call
         <CODE>GetAuntieDialog</CODE> and then
         <CODE>AppendDialogItemsAsControls</CODE> before allowing the
         user to interact with the window, you won't have to work
         around the bizarre non-linear drawing behavior you would
         have gotten from Dialog Manager.</P>
         
         <P><CODE>AppendDialogItemsAsControls</CODE> auto-embeds
         controls just as Dialog Manager does.</P>
         
         <P>Unlike Dialog Manager, when
         <CODE>AppendDialogItemsAsControls</CODE> creates user pane
         controls based on dialog user items, the user pane controls
         support embedding, so if you need a user pane control which
         doesn't support embedding, you'll need to create an
         appropriate control resource.</P>
         
         <P>If you pass a NIL reference provider,
         <CODE>AppendDialogItemsAsControls</CODE> will append
         controls whose IDs have been assigned in the default
         manner.</P>
         
         <H3>NewAuntieDialog</H3>
         
         <PRE>OSStatus NewAuntieDialog
   (short resID, WindowRef window, ControlCreationProcPtr);<BR></PRE>
         
         <P>This function is roughly analgous to
         <CODE>NewDialog</CODE>. <CODE>NewAuntieDialog</CODE> calls
         <CODE>AppendDialogItemsAsControls</CODE> and is called by
         <CODE>GetAuntieDialog</CODE>. The resource ID specifies the
         dialog item list resource which is to be used as a template.
         This function allows you to create your own window, perhaps
         with an API such as <CODE>CreateNewWindow</CODE>, and pass
         it to the AuntieDialog package to be populated with
         controls. This function will fail if the window already
         contains controls.</P>
         
         <H3>GetAuntieDialog</H3>
         
         <PRE><CODE>OSStatus GetAuntieDialog
    (short resID, WindowRef behind,
       ControlCreationProcPtr, WindowRef *result);<BR></CODE></PRE>
         
         <P><CODE>GetAuntieDialog</CODE> is roughly analagous to
         <CODE>GetNewDialog</CODE>. <CODE>resID</CODE> is the
         resource ID of the dialog resource from which you want to
         build a window. <CODE>behind</CODE> is the window behind
         which you wish the new window to appear. And
         <CODE>result</CODE> is where the reference to the newly
         created window will be put.</P>
         
         <P><CODE>GetAuntieDialog</CODE> produces a
         <CODE>WindowRef</CODE>, not a <CODE>DialogRef</CODE>. Don't
         expect the window that's produced to be recognized by the
         Dialog Manager. It's not a dialog. That would defeat the
         purpose of AuntieDialog.</P>
         
         <P><CODE>GetAuntieDialog</CODE> always creates a color
         window and ignores any dialog color table resource in favor
         of the colors and patterns in the theme the user has chosen.
         <CODE>GetAuntieDialog</CODE> also ignores any dialog
         extension resource and creates the window as if such a
         resource with all its bits set is present, which means you
         always get a theme background and you always get theme
         controls. (The remaining bits are handled elsewhere in
         AuntieDialog or by your program.)</P>
         
         <P><CODE>GetAuntieDialog</CODE> ignores any extra bits on
         the end of the dialog template resource which the Dialog
         Manager would have used to align the window. The support for
         these bits has never been well-defined or bug-free, and
         <CODE>RepositionWindow</CODE>, an API introduced in Mac OS
         8.5, is so vastly superior to the old alignment support that
         I decided it was better to leave the issue unaddressed.</P>
         
         <P><CODE>GetAuntieDialog</CODE> always creates the window
         initially invisible and only shows it (if the dialog
         resource specifies the window is to be shown) after creating
         all the controls. This minimizes flicker.</P>
         
         <H3>DisposeChildControls</H3>
         
         <PRE>OSStatus DisposeChildControls (ControlRef);<BR></PRE>
         
         <P>This function disposes the child controls of the given
         parent control. This will be useful mostly in conjunction
         with <CODE>AppendDialogItemsAsControls</CODE>; you may wish
         to delete some controls and append some others, for example
         when the user clicks a new tab in a tab control you're using
         as a parent control.</P>
         
         <P>This function differs from <CODE>DisposeControl</CODE> in
         that <CODE>DisposeControl</CODE> would dispose the parent in
         addition to the children.</P>
         
         <P>This function differs from <CODE>ShortenDITL</CODE> not
         only in that it operates on controls and not dialog items
         but also in that it removes child controls regardless of
         where they appeared in the dialog item list before they
         became controls. This may create holes in the range of
         control IDs. You may want to make sure that the child
         controls you dispose appear at the end of the dialog item
         list they came from. That way, when you append other
         controls, their IDs will be in sequence with the rest of the
         controls in the window.</P>
         
         <H2>
         
         <HR>
         
         <BR>
         To do:</H2>
         
         <UL>
            <LI>Add more application-level logic to MegaDialog
            simulation.<BR>
            <BR>
            </LI>
            
            <LI>Describe all the functions, including the
            callbacks.<BR>
            <BR>
            </LI>
            
            <LI>Passing the root control to DeactivateControl to
            propagate latent grayness doesn't always work properly on
            older systems. Should we work around this?<BR>
            <BR>
            </LI>
            
            <LI>AuntieDialogGetIdleInterval should traverse every
            control in every AuntieDialog window to determine which
            control would request the shortest idle if it could.<BR>
            <BR>
            </LI>
            
            <LI>Finish the compatibility hacks in
            ShouldDrawControlsOffScreen.<BR>
            <BR>
            </LI>
            
            <LI>Work around Radar 2459917 (not enough mouse-moved
            events)</LI>
         </UL>
      </TD>
   </TR>
</TABLE>
</P>
</BODY>
</HTML>