Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Design

Object C and the Macintosh Control Panel


SP 89: OBJECT C AND THE MACINTOSH CONTROL PANEL

Object-oriented cdev development

Bryan Waters is a software engineer for Maynard Electronics and can be reached at 460 E Semoran Blvd., Casselberry, FL 32707.


With the addition of object-oriented extensions to Think C in Version 4.0, Think Technologies adopted the notion of "Object C," which combines the functionality of Object Pascal with the syntax of C++. While Object C supports the declaration of classes, sub-classes, inheritance, and methods, it does not support function and operator overloading, constructors and destructors, data hiding through the use of public and private keywords, or in-line functions. In this article, I'll show how Object C can be used to simplify the development of Macintosh resources, and I'll use a control panel device as an example.

A class in Object C is declared (as shown in Example 1) where class_name is the name of the class, and storage_class can be either direct or indirect. This determines whether the object is allocated as a pointer or a handle.

Example 1: Declaring an Object C class

  struct class_name : storage_type {
          variable and/or method declarations
          .
          .
          .
  } ;

To declare a method, simply include the routine's prototype in the class declaration. Subclasses are defined using the syntax shown in Example 2.

Example 2: Declaring an Object C subclass

  struct subclass_name:class_name {
          variable and/or method declaration ;
          /* NOTE: in order to override a method in the  class,
  simply redeclare the method in the ~ subclass */
  } ;

Methods are defined by using the class_name combined with the method name, separated by the scope operator': :'. The object itself is passed implicitly to the method, and can be referenced either explicitly by using the keyword this, or implicitly by referencing the field or method directly. (All of the new keywords are interpreted in context, and do conflict with variable naming; for example, you can have a non-object variable named this, although I wouldn't recommend it.)

Objects are allocated and destroyed using the functions new()and delete(). Memory that was not allocated using these functions can be treated like an object, by using the functions bless() and blessD(), respectively, for pointers and handles. The final function is a member(), which is used to determine whether an object is a member of a specific class.

Control Panel Devices

With the release of the Macintosh SE, the Control Panel desk accessory became extendible through the use of cdev, a control panel device. This allowed programmers to add their own utilities to the control panel, without using a precious desk accessory slot (currently a maximum of 15 desk accessories is allowed). The cdev takes the form of a resource file that is placed in the Macintosh's system folder. When the Control Panel DA is opened, it searches the system folder for all files of type cdev, and adds them to its list. A cdev must have a list of mandatory resources before being adopted by the control panel. This list includes the 'cdev' code resource (an icon used to represent itself in the control panel's list), a dialog item list and 'nrct' resource (which determine the cdev's interface), and a 'mach' resource (a bitmask used to determine which Mac the 'cdev' should appear on; for example, a 'cdev' to change menu bar colors, would not be appropriate for a Macintosh without color QuickDraw). The 'cdev' resource contains the code to implement the cdev and is called from the control panel in the format shown in Example 3.

Example 3: Format for calling a cdev

  pascal long cdev ( message, Item, numItems, CPanelID, ev,
  cdevValue CPDialog )
  int message, Item, numItems, CPanelID;
  EventRecord *ev ;
  long cdevValue ;
  DialogPtr CPDialog ;

The control panel communicates with the 'cdev' by passing messages pertaining to a specific event or requesting a specific action. For example, when the cdev is first selected, the control panel calls the 'cdev' code resource with the InitDev message. The messages are listed in Table 1.

Table 1: Messages passed between the control panel and cdev

  Message        Meaning
  -------------------------------------------------------

  initDev        initialization

  hitDev         mouse down inside of a cdev dialog item

  closeDev       cleanup

  nulDev         idle time message

  updateDev      update event message

  activDev       activate event message

  deActivDev     de-activate event message

  keyEvtDev      key down event message

  macDev         used for checking machine characteristics (this
                 is only used if the 'mach' resource does
                 not define the environment that the cdev
                 should be used in).

  undoDev        the user selected undo from the menu.

  cutDev         the user selected cut from the menu.

  copyDev        the user selected copy from the menu.

  pasteDev       the user selected paste from the menu.

  clearDev       the user selected clear from the menu.

OOP and the Control Panel

Included with Think C 4.0 is an object-oriented cdev development shell that I will use to develop a typical cdev. The cdev development shell is based around a cdev class, which implements the basics of a control panel device. To use this to write a cdev, you must override the methods needed by your cdev. The example I chose (see Listing One, page 86) uses the SysEnvirons() routine to obtain the current system information for display purposes; basically a system info cdev.

To implement this, you need to define a subclass that overrides the Init() method, which corresponds to the initDev message passed to the cdev-resource by the control panel. Also, you need to provide a routine named Runnable(), which examines the environment and determines whether the cdev should be shown or not. This corresponds to the macDev message, which cannot be implemented as a method because it will be called before the object is allocated. Finally, the New routine must be provided to allocate the subclass and return the object reference.

Conclusion

The cdev shell included with Think C 4.0 makes cdev development relatively easy. Essentially all you have to do is write the application-specific portions of the control panel device, a technique that can be used for the development of any Macintosh code resources like control and window definition procedures, and even device drivers. (For an example of a driver shell in Object C, see my article entitled "Writing Macintosh Device Drivers" in this issue on page 37.)

_OBJECT C AND THE MACINTOSH CONTROL PANEL_ by Bryan Waters

[LISTING ONE]

<a name="0298_000c">

#include <DialogMgr.h>      /* for dialog routines */
#include <cdev.h>      /* for cdev class */

/* define subclass of class cdev */
struct sys_info:cdev {
   void Init( ) ;      /* override the initDev message */
};
/* dialog item numbers */
#define MACH_TYPE   1
#define SYS_VER    2
#define PROC_TYPE   3
#define HAS_FPU    4
#define HAS_CQD    5
#define KBD_TYPE   6
#define AT_VER      7

/* current system environment version */
#define curSysEnvVers   1

/* computer model */
#define envMachUnknown   0
#define env512KE      1
#define envMacPlus      2
#define envSE         3
#define envMacII      4
#define envMacIIx      5
#define envMacSE30      7

/* cpu types */
#define envCPUUnknown   0
#define env68000      1
#define env68010      2
#define env68020      3
#define env68030      4

/*  keyboard types  */
#define envUnknownKbd   0
#define envMacKbd      1
#define envMacAndPad   2
#define envMacPlusKbd   3
#define envAExtendKbd   4
#define envStandADBKbd   5

/* Will the cdev be usable in this environment? */
/* This routine should be used to determine whether the cdev
   should appear in the control panel for this environment. An
   example of this would be a cdev for setting the parameters for
   the virtual memory manager for System 7.0. This cdev
   would only be necessary for 68020 with the Paged Memory
   Management Unit, or a 68030.  In this case, the Runnable
   routine would determine the type of machine it was on, and
   return either TRUE or FALSE, depending on whether the cdev
   serves a valid purpose on this machine */
Boolean Runnable( )
{
   return TRUE ;
}

/* This routine creates the cdev. In our cdev, it doesn't really
   serve any purpose, since we don't have any storage
   requirements, but we still need the structure allocated for
   purposes of calling the appropriate methods */
cdev *New()
{
   return new( sys_info ) ;
}

/* This routine overrides the initDev message, which is called
   upon selection of any item in the control panel list.  For a
   more complex cdev, this routine would do all initialization
   required for the cdev, and the more complex interaction
   required by the cdev would be implemented using remainder of
   the messages, and their handlers. The remainder of the
   messages, and the related method, are listed below:

   hitDev - for mouse downs.  Method: ItemHit( )
   closeDev - for de-initialization.  Method: Close( )
   nulDev - idle routine, for whatever needs to be done on a
            regular basis, but is not really event related.
            Method: Idle( )
   updateDev - for update events.  Method: Update( )
   activDev - for activate events.  Method: Activate( )
   deActivDev - for de-activate events.  Method: Deactivate( )
   keyEvtDev - for key downs.  Method: Key( )
   undoDev - for handling undos.  Method: Undo( )
   cutDev - for handling cuts.   Method: Cut( )
   copyDev - for handling copys.  Method: Copy( )
   pasteDev - for handling pastes.  Method: Paste( )
   clearDev - for handling clears.  Method: Clear( )

   To implement any of these messages, simply override the
   method, in your subclass.

*/
void sys_info::Init( )
{
   int type ;
   Handle hdl ;
   Rect box ;
   SysEnvRec sys_env ;
   long tmp ;
   int count ;
   char *curr1 ;
   char *curr2 ;
   char buffer1[10] ;
   char buffer2[10] ;

   /* first call inherited Init routine */
   /* NOTE: this routine must be called to set up the cdev class */
   inherited::Init();

   /* get system environment */
   SysEnvirons( curSysEnvVers, &sys_env ) ;

   /* set machine type */
   GetDItem( this->dp, MACH_TYPE + this->lastItem, &type,   &hdl, &box ) ;
   switch( sys_env.machineType ) {
   default:
   case envMachUnknown:
      SetIText( hdl, "\punknown" ) ;
      break ;
   case envMacPlus:
      SetIText( hdl, "\pMacintosh Plus" ) ;
      break ;
   case envSE:
      SetIText( hdl, "\pMacintosh SE" ) ;
      break ;
   case envMacII:
      SetIText( hdl, "\pMacintosh II" ) ;
      break ;
   case envMacIIx:
      SetIText( hdl, "\pMacintosh IIx" ) ;
      break ;
   case envMacSE30:
      SetIText( hdl, "\pMacintosh SE/30" ) ;
      break ;
   }

   /* set system version */
   GetDItem( this->dp, SYS_VER + this->lastItem, &type,  &hdl, &box ) ;
   if( sys_env.systemVersion == 0 ) {
      SetIText( hdl, "\punknown" ) ;
   }else{
      count = 0 ;
      curr1 = &buffer1[1] ;
      tmp = ( sys_env.systemVersion & 0xFF00 ) >> 8 ;
      NumToString( tmp, buffer2 ) ;
      curr2 = &buffer2[1] ;
      while( buffer2[0]-- ) {
         count++ ;
         *curr1 = *curr2 ;
         curr1++ ; curr2 ++ ;
      }
      count++ ;
      *curr1 = '.' ;
      curr1++ ;
      tmp = ( sys_env.systemVersion & 0x00FF ) ;
      NumToString( tmp, buffer2 ) ;
      curr2 = &buffer2[1] ;
      while( buffer2[0]-- ) {
         count++ ;
         *curr1 = *curr2 ;
         curr1++ ; curr2 ++ ;
      }
      buffer1[0] = count ;
      SetIText( hdl, buffer1 ) ;
   }

   /* set processor type */
   GetDItem( this->dp, PROC_TYPE + this->lastItem, &type,   &hdl, &box ) ;
   switch( sys_env.processor ) {
   default:
   case envCPUUnknown:
      SetIText( hdl, "\punknown" ) ;
      break ;
   case env68000:
      SetIText( hdl, "\pMotorola 68000" ) ;
      break ;
   case env68010:
      SetIText( hdl, "\pMotorola 68010" ) ;
      break ;
   case env68020:
      SetIText( hdl, "\pMotorola 68020" ) ;
      break ;
   case env68030:
      SetIText( hdl, "\pMotorola 68030" ) ;
      break ;
   }

   /* set has FPU */
   GetDItem( this->dp, HAS_FPU + this->lastItem, &type, &hdl, &box ) ;
   SetIText( hdl, sys_env.hasFPU ? "\pyes":"\pno" ) ;

   /* set has colorQD */
   GetDItem( this->dp, HAS_CQD + this->lastItem, &type, &hdl, &box ) ;
   SetIText( hdl, sys_env.hasColorQD ? "\pyes":"\pno" ) ;

   /* set keybord type */
   GetDItem( this->dp, KBD_TYPE + this->lastItem, &type,  &hdl, &box ) ;
   switch( sys_env.keyBoardType ) {
   default:
   case envUnknownKbd:
      SetIText( hdl, "\punknown" ) ;
      break ;
   case envMacKbd:
      SetIText( hdl, "\pstandard" ) ;
      break ;
   case envMacAndPad:
      SetIText( hdl, "\pstandard with key pad" ) ;
      break ;
   case envMacPlusKbd:
      SetIText( hdl, "\pMacintosh Plus Keyboard" ) ;
      break ;
   case envAExtendKbd:
      SetIText( hdl, "\pApple extended" ) ;
      break ;
   case envStandADBKbd:
      SetIText( hdl, "\pstandard ADB" ) ;
      break ;
   }

   /* set appletalk version */
   GetDItem( this->dp, AT_VER + this->lastItem, &type,  &hdl, &box ) ;
   if( sys_env.atDrvrVersNum == 0 ) {
      SetIText( hdl, "\pnot loaded" ) ;
   }else{
      tmp = ( sys_env.systemVersion & 0xFF00 ) >> 8 ;
      NumToString( tmp, buffer1 ) ;
      SetIText( hdl, buffer1 ) ;
   }

   /* finished */
   return ;
}


Example 1: Declaring an Object C class

struct class_name : storage_type {
   variable and/or method declarations ;
   .
   .
   .
} ;

Example 2: Declaring an Object C subclass

struct subclass_name:class_name {
   variable and/or method declaration ;
   /* NOTE: in order to override a method in the   class,
simply redeclare the method in the   subclass */
} ;


Example 3: Format for calling a cdev

pascal long cdev( message, Item, numItems, CPanelID, ev,
cdevValue CPDialog )
int message, Item, numItems, CPanelID ;
EventRecord *ev ;
long cdevValue ;
DialogPtr CPDialog ;










Copyright © 1989, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.