TweetFollow Us on Twitter

Hello TCL World
Volume Number:8
Issue Number:4
Column Tag:TCL Workshop

Hello TCL World

An object lesson

By Martin Minow, MacTutor Regular Contributing Author

Note: Source code files accompanying article are located on MacTech CD-ROM or source code disks.

For the past five years, I’ve been a volunteer in the Boston Marathon press-room, where I am responsible for calculating the split (intermediate) times during the race. I did this by hand for two years, then decided a “simple little” computer program would make my life easier.

This “simple little” program grew - quietly - for two years when, last winter, I decided it was time to rewrite it from scratch, adding in all of the Mac-like things I didn’t understand when I first attacked the problem. To make matters worse, I decided to write the program using the Think Class Library (TCL) - it was a chance to learn to apply object-oriented techniques to a real problem, and the library would be responsible for all the grotty details of the Mac interface, such as how to scroll stuff around.

By the time the marathon came around, the redesign had spawned a monster of 24,000 lines of C (about twice the size of the original) that used System 7.0’s built-in interprocessor communications facilities to move data between the seven computers that we use to enter and display data.

While the Think Class Library is an excellent piece of work, the documentation could go further in clarifying how the pieces fit together. The two sample programs in the TCL distribution are useful, but will intimidate anyone attacking TCL for the first time.

Traditionally, C programmers start out by writing a program that does nothing more than print a greeting on the terminal:

/* 1 */

#include <stdio.h>
main() {
 printf(“hello world!\n”);
}

Having written the run-time library for a C compiler, I can assure you that, while “hello world!” is a simple program for a computer language user to write, it is not a trivial task for the compiler and its support library. Also, before the program runs, the programmer must master the editor and enough of the command environment to be able to compile, link, and execute a program.

The Macintosh and Think C environment simplify the edit and command environment. However, writing the TCL equivalent of “hello world!” remains a non-trivial task. If I’m successful, the enclosed program gives you both a reasonable introduction to a complex environment and a few simple tools you can use in other, more demanding, applications. The program requires the following files:

• Hello.c Just the main program: it creates the application class,

initializes it, runs it and terminates it.

• Hello.r Resources (in RMaker format) for the application.

• Hello.Π The Think C project file.

• Hello.Π.rsrc The compiled resource file.

• HelloApp.c The application class.

• HelloApp.h The header file that defines the application class

• oopsDebug.c My version of the oops.c support file that catches some

common programming errors.

• TCL Resources The common resource file for TCL projects.

• AuxWindow.c A generic status window class.

• AuxWindow.h The header file that defines the AuxWindow class.

• TextWindow.c A simple “write text to a window” class.

• TextWindow.h The header file that defines the TextWindow class.

• TextPane.c A CPane that can draw text.

• TextPane.h The header file that defines the TextPane class.

The application is built together with a large number of TCL methods which are described in the Think C documentation. When you run it, it displays a window on the screen, sets the window title (of course) to “Hello World!” and waits for a menu selection. The only menu items that actually do anything are Quit and Show/Hide Window: others generally result in some sort of message being displayed in the window.

The rest of this article describes the various classes and methods, and attempts to explain how they fit together. Read through the code, then attack the Class Library on your own.

The Hello.c Main Program

Hello.c contains the main program. All it does is create an instance of the HelloApp application class, initialize it, run it and exit.

/* 2 */

#include "HelloApp.h"

void main()
{
 gApplication = (HelloApp *) new(HelloApp);
 gApplication->IHelloApp();
 gApplication->Run();
 gApplication->Exit();
}

The HelloApp Application Class

HelloApp is the main application: its initialization method creates a TextWindow, sets its title, and displays a string. The other classes contain support for TextWindow and are reasonably straight-forward.

You will probably find the UnknownCommand method useful (I sure did!) - it checks whether a command is one that is normally executed by the CApplication. If not, the command number or menu number and menu item are displayed in the TextWindow. The reason this is needed is that TCL ignores unrecognized commands. However, when you’re debugging a new application, you will probably forget to set the gopher correctly, or send a command to a class that doesn’t understand it.

If a class’ method doesn’t understand a command, it will be passed up the supervisory class chain. This will eventually call DoCommand in the application class - your program’s last chance to process the command. UnknownCommand shows one reasonable use of the TextWindow class. Although I’ve shown it as part of the application class, I usually keep UnknownCommand in a Utility class, along with a few other general-purpose classes. A gUtility global variable offers a common reference to the Utility class methods.

The AuxWindow Display Window Class

AuxWindow defines a simple status window, based on the CClipboard class. This window can be displayed or hidden without loss of data. As with the CClipboard, it is intended to be an auxiliary to your document or application: it does not process New or Open (or Save) commands, for example. The utility class is built on AuxWindow.

One of the most frustrating things I found in learning the TCL was my assumption that the each document had one and only one window. It took a long time before I understood how to create a second “status” window. AuxWindow shows a simple, but practical, status window that you can add to any class. I used the CClipboard class as original model for this window. Like CClipboard, it allows the window to be hidden and re-shown. In order to use it, you must define two resources: a window definition that gives the window type and its overall dimensions and a STR# resource that specifies the text of the Show and Hide menu commands.

Although you will think of AuxWindow as a window into which your application will draw text, TCL sees it as a CDirector sub-class that ties a visual structure (CWindow and CPanes) together with control (DoCommand) and data hierarchies. If you want to live comfortably in the TCL world, you should make a strong effort to separate classes in this manner. However, this is easier said than done. After several generations of writing and re-writing, I finally reorganized the class hierarchy into two independent classes (that may be used as-is in other projects) and one task-specific class that controls the entire process. The advantage of this is that you can drop AuxWindow and TextPane into another project without change, and write a small application-specific AuxWindow sub-class (TextWindow here) that does what your application needs while the hard stuff (showing and hiding windows; drawing and scrolling text) is hidden away in TCL and your general-purpose classes.

AuxWindow Window Management

TCL thinks of windows as being inexorably tied to documents: they are created when documents are opened or created, and are destroyed when a document is closed. This doesn’t work for status windows, tool palettes, and similar non-document windows. While these may be created along with a document, you may want them to be displayed, hidden, and re-displayed under user control. TextWindow permits this by overriding several CDirector methods.

The Macintosh human interface guidelines recommend that status windows be hidden when the application is suspended. AuxWindow follows these guidelines if you set the hideOnSuspend parameter TRUE when the window is created.

AuxWindow doesn’t know very much: about all it can do is create a window and control its visiblity. The actual behavior is controlled by the TextWindow class.

The TextWindow Status Window Class

TextWindow is a utility class that you might find useful to add to your own projects. It uses AuxWindow to create a window on the screen that allows your application to write text messages. It is an extremely simple class: all it does is create and initialize a single TextPane class instance. By making TextWindow a sub-class to AuxWindow, the programmer doesn’t have to deal with the mess of showing and hiding windows. All of the status windows in my application could be made sub-classes of AuxWindow (which is something I wish I realized when I wrote the program in the first place).

The TextPane Class

TextPane does the actual drawing. It is reasonably straight-forward. Note that the TextPane doesn’t care where it’s positioned. You could easily create a window with several TextPanes if you need to display several different types of status information. TextPane could (and perhaps should) be extended to allow the user to change the font or font size, or to change the number of lines that are displayed when the size of the pane (or its enclosing window) changes.

Most of the work of TextPane is done by its initialization method. Because I’ve simplified the class so it has a single, pre-specified font and font size, and the pane cannot be re-sized, the initialization method computes the height of each line and the number of lines of data that will be stored and displayed.

Note that my code locks the string vector (vectorHandle) before initializing strings. This is needed because the statement

/* 3 */

theStrings[i] = NewString((StringPtr) “\p”);

might be compiled so as to compute the address of theStrings[i] before calling NewString() to create the string handle. Since NewString() may move memory and theStrings[i] is in a TCL handle-based object (which can be moved), the address may be incorrect. This is not a compiler error!

Putting it all Together

The project is shown in Figure 1, above. Note that even a simple class requires a large number of TCL classes. oopsDebug.c is a minor revision of oops.c that traps NULL method calls. (I sometimes wonder whether good programmers write good debugging environments or whether they just never make mistakes. I suppose I’ll know if I ever meet a programmer that doesn’t make mistakes.)

Write it, Rewrite it, then Rewrite it Again

While programming the marathon splits application, I wrote about 5 variants of the AuxWindow class: status and display windows that were sub-windows to the actual document. I took one of these and turned it into the original AuxWindow, then rewrote it three or four times in order to break apart its monolithic “window + display + data” structure into smaller, reusable, components.

The final revision yielded two general-purpose classes: a window that can be displayed and controlled independently of other windows in my application, and a pane that can display lines of text. Note that neither knows about the other: only the TextWindow class connects the display to its window.

The moral is to write your code carefully, and realize that you will rewrite it several times before its true structure emerges from the fog of your misunderstandings.

OopsDebug.c

My most common error is failing to initialize a variable before using it. If the variable is a class instance, and I try to send that class a message, my program will crash. In order to simplify debugging, I modified the method dispatch routines in oops.c. Note, however, that these modifications are not approved by Symantec (who were, however, kind enough to give me permission to reprint my modifications.) If you do decide to use oopsDebug.c, please be aware that you will have to remove it when Symantec releases a future version of Think C.

Acknowledgements and Copyright

Thanks to Mike Morton for proofreading and other suggestions. Thanks, especially, to Symantec Tech Support for answering all sorts of questions that began with “I’m just learning TCL and I can’t figure out how to ” with somewhat more politeness than I might deserve. Thanks also for permission to reprint oopsDebug.c.

This software is Copyright © 1991 Martin Minow and Xplain Corporation. You may use these classes in your programs without restrictions as long as you accept responsibility for their use. You may not redistribute the source for profit without permission.

Listing: HelloApp.h

#define _H_HelloApp/* Include this file only once */
#include <CApplication.h>
#include "TextWindow.h"

/*
 * Some of these values must track values in the
 * resource file.
 */
enum {  /* Resources & Commands  */
 WIND_Note= 1024,
 MENU_Test= 1024,
 MENU_MakeTextWindow = 1025,
 STRS_Toggle= 1024,
 CmdToggleTextWindow = 1024,
 CmdTest, /* Unknown command*/
 CmdMakeNoFloatHide,
 CmdMakeNoFloatShow,
 CmdMakeIsFloatHide,
 CmdMakeIsFloatShow
};

extern struct HelloApp    *gApplication;

struct HelloApp : CApplication {
 TextWindow *itsTextWindow;
 
 /*
  * Create the application.
  */
 void   IHelloApp(void);
 /*
  * These are CApplication methods that
  * are overridden for our purposes.
  */
 void   MakeDesktop(void);
 void   SetUpMenus(void);
 void   UpdateMenus(void);
 void   DoCommand(long    theCommand);
 /*
  * Create a new text window. theCommand has
  * two purposes: it defines the particular
  * window type and selects the show/hide
  * command text.
  */
 void   MakeTextWindow(long theCommand);
 /*
  * UnknownCommand is a utility class that
  * logs a warning message if a command wasn't
  * processed by the application.  Commands
  * that are normally processed by CApplication
  * (such as cmdQuit) are not logged.
  */
 void   UnknownCommand(long theCommand);
};
Listing: HelloApp.c
/*  Copyright © 1991 Martin Minow. All rights reserved.
 * All use and non-commercial distribution permitted.
 */
#include <CBartender.h>
#include <CBureaucrat.h>

#include <CDesktop.h>
#include <CFWDesktop.h>
#include <Commands.h>
#include <TBUtilities.h>
#include "HelloApp.h"

extern CBartender*gBartender;
extern CDesktop  *gDesktop;
extern CBureaucrat *gGopher;

/*
 * IHelloApp -- the only thing this does is display the
 * logging window on the screen. It reacts to commands
 * by displaying the command number and sending them
 * onwards to the TCL CApplication method.
 */
void
HelloApp::IHelloApp(void)
{
 CApplication::IApplication(8, 32768L, 4096L);
 itsTextWindow = NULL;
 /* Normal window*/
 MakeTextWindow(CmdMakeNoFloatHide);
 itsTextWindow->SetTitle(
 (StringPtr) "\pHello (TCL) World!");
 itsTextWindow->SetText((StringPtr) "\pHello world!");
 gGopher = this;
}

/*
 * Override MakeDesktop to allow creation of floating
 * windows.
 */
void
HelloApp::MakeDesktop()
{
 gDesktop = new(CFWDesktop);
 ((CFWDesktop *) gDesktop)->IFWDesktop(this);
}

/*
 * SetupMenus is called (by CApplication::IApplication).
 * It adds our application-specific menus to the menu bar.
 */
void
HelloApp::SetUpMenus()
{
 Str255 work;
 short  MENUid;
 short  itemNo;
 MenuHandle macMenu;
 
 inherited::SetUpMenus();
 /*
  * Add some menu items to test UnknownCommand
  */
 gBartender->SetCmdText(cmdAbout, 
 (StringPtr) "\pAbout Hello World");
 gBartender->SetDimOption(MENU_Test, dimNONE);
 gBartender->SetDimOption(MENU_MakeTextWindow, dimNONE);
 AddResMenu(GetMHandle(MENUfont), 'FONT');
 gBartender->SetDimOption(MENUfont, dimNONE);
 gBartender->SetUnchecking(MENUfont, TRUE);
 /*
  * Build the TextWindow menu command on the fly.
  * You can also just add the command to a menu
  * in your resource file.
  */
 GetIndString(work, STRS_Toggle, kShowAuxWindow);
 gBartender->FindMenuItem(cmdToggleClip,
 &MENUid, &macMenu, &itemNo);
 if (macMenu != NULL && work[0] != 0) {
 gBartender->InsertMenuCmd(CmdToggleTextWindow,
 work, MENUid, itemNo);
 }
}

/*
 * UpdateMenus is called before showing a menu. Make sure
 * the Show/Hide text box command is enabled.
 */
void
HelloApp::UpdateMenus()
{
 inherited::UpdateMenus();
 if (itsTextWindow != NULL)
 gBartender->EnableCmd(CmdToggleTextWindow);
}

/*
 * Execute a command.  Only CmdToggleTextWindow does
 * anything useful.
 */
void
HelloApp::DoCommand(long  theCommand)
{
 switch (theCommand) {
 case CmdMakeNoFloatHide:
 case CmdMakeNoFloatShow:
 case CmdMakeIsFloatHide:
 case CmdMakeIsFloatShow:
 MakeTextWindow(theCommand);
 break;
 case CmdToggleTextWindow:
 itsTextWindow->Toggle(); break;
 case cmdAbout:
 if (itsTextWindow == NULL)
 MakeTextWindow(CmdMakeNoFloatHide);
 itsTextWindow->SetTitle(
 (StringPtr) "\pHello (TCL) World!");
 itsTextWindow->SetText(
 (StringPtr) "\pHello world!");
 itsTextWindow->SetText(
 (StringPtr) "\pCopyright © 1991"
 " Martin Minow,"
 " All rights reserved.");
 break; 
 case cmdNew:
 if (itsTextWindow != NULL)
 itsTextWindow->SetText(
 (StringPtr) "\pNew selected");
 break;
 case cmdOpen:
 if (itsTextWindow != NULL)
 itsTextWindow->SetText(
 (StringPtr) "\pOpen selected");
 break;
 default:
 /*
  * I've found it useful to log commands
  * that aren't executed by my application
  * This catchs errors in setting the gopher
  * in a complicated web of sub-classes.
  */
 UnknownCommand(theCommand);
 inherited::DoCommand(theCommand);
 }
}

void
HelloApp::MakeTextWindow(long theCommand)
{
 BooleanisFloating;
 BooleanhideOnSuspend;
 Str255 titleText;
 
 if (itsTextWindow != NULL)
 itsTextWindow->Dispose();
 isFloating = (theCommand == CmdMakeIsFloatHide
  ||  theCommand == CmdMakeIsFloatShow);
 hideOnSuspend = (theCommand == CmdMakeNoFloatHide
  ||  theCommand == CmdMakeIsFloatHide);
 itsTextWindow = new(TextWindow);
 itsTextWindow->ITextWindow(
 this,  /* The application*/
 WIND_Note, /* WIND Resource id  */
 isFloating,/* Not floating */
 hideOnSuspend,  /* Don't hide*/
 CmdToggleTextWindow,/* Show/Hide cmd*/
 STRS_Toggle,    /* Show/Hide STR# */
 (StringPtr) "\pgeneva",  /* Font name */
 9 /* Font size value*/
 );
 gBartender->GetCmdText(theCommand, titleText);
 itsTextWindow->SetTitle(titleText);
 itsTextWindow->Show();
}

/*
 * Unrecognized commands (from the application) come here.
 * This method does nothing for TCL commands (cmdQuit,
 * cmdNull) that are normally handled by the TCL
 * CApplication method, and logs the command or menu
 * number for those the application should have handled.
 * If commands are logged, they may indicate menu items
 * are enabled incorrectly, the Gopher set incorrectly,
 * or a class failing to handle commands directed at it.
 */
void
HelloApp::UnknownCommand(long theCommand)
{
 short  theMenu;
 short  theItem;
 Str255 work;
 Str255 number;
 
 theMenu = HiShort(-theCommand);
 theItem = LoShort(-theCommand);
 if (theCommand < 0 && theMenu == MENUapple)
 ; /* These are aways ok  */
 else {
 switch (theCommand) {
 case cmdNull:
 case cmdQuit:
 case cmdToggleClip:

 break;
 default:
 if (theCommand >= 0) {
 CopyPString((StringPtr) "\pUnknown command ",
 work);
 NumToString(theCommand, number);
 }
 else {
 CopyPString((StringPtr) "\pUnknown menu ",
 work);
 NumToString(theMenu, number);
 ConcatPStrings(work, number);
 ConcatPStrings(work, (StringPtr) "\p.");
 NumToString(theItem, number);
 }
 ConcatPStrings(work, number);
 if (itsTextWindow != NULL)
 itsTextWindow->SetText(work);
 }
 }
}
Listing: AuxWindow.h
/*
 * AuxWindow
 * Superclass: CDirector
 */ 
#define _H_AuxWindow
#include <CApplication.h>
#include <CDirector.h>

enum {
 kShowAuxWindow = 1,
 kHideAuxWindow
};

struct AuxWindow : CDirector {
 long   itsToggleCmd;
 short  itsMenuNames;
 BooleanwindowVisible;
 BooleanhideOnSuspend;
 
 /*
  * Create an auxiliary window.  The
  * supervisor must be the application.
  * the window id is a WIND resource.
  * isFloating and isHiddenOnSuspend
  * configure the window.
  * toggleCmd and showHideRes are used
  * to control its Show/Hide menu command.
  */
 void         IAuxWindow(
 CApplication  *aSupervisor,
 short  aWindowId,
 BooleanisFloating,
 BooleanisHiddenOnSuspend,
 long   toggleCmd,
 short  showHideRes);
 /*
  * Change the window's title string.
  */
 void   SetTitle(StringPtraTitle);
 /*
  * Override some CDirector classes to allow
  * the window to be hidden and re-shown.
  */
 void   Suspend(void);
 void   Resume(void);
 BooleanClose(Booleanquitting);
 void   CloseWind(CWindow *theWindow);
 void   Show(void);
 void   Hide(void);
 /*
  * The application calls Toggle to change
  * the AuxWindow visibility.  (Show and/or
  * Hide may also be called, of course.)
  */
 void   Toggle(void);
};
Listing: AuxWindow.c
/*
 * AuxWindow
 * Superclass: CDirector.
 * Copyright © 1991 Martin Minow. All Rights Reserved.
 * All use and non-commercial distribution permitted.
 * Set tabs every 4 bytes.
 *
 * This class implements a generic auxiliary window that
 * may be used in addition to the document's normal
 * window.  It is based on the CClipboard class.
 */

#ifdef DOCUMENTATION

Title   AuxWindow
SuperclassCDirector
SubclassesTextWindow

usage

 #include "AuxWindow.h"
 
 void   IAuxWindow(
 CApplication  *aSupervisor,
 short  aWindowId,
 BooleanisFloating,
 BooleanhideOnSuspend,
 long   toggleCmd,
 short  showHideRes);

 Initialization.  itsSupervisor must be the
 application.  It displays the window defined
 by aWindowId.  See the description of CWindow
 for information on floating windows.
 
 hideOnSuspend is TRUE if this window should follow
 the Macintosh human interface guidelines, which
 recommend that floating windows and general status
 windows be hidden when your application is suspended.
 Set it FALSE if this window should remain visible
 except when the user explicitly hides it via the
 menu command (Toggle method) or by clicking in
 the close box.
 
 toggleCmd is the command number your application
 uses to make the window visible and invisible.
 If you don't have a show/hide menu command, just
 pass cmdNull and set showHideRes to zero. Otherwise,
 your application class should create a menu item
 using some variant on the following sequence:
 
   Str255 work;
   shortMENUid;
   MenuHandle    macMenu;
   shortitemNo;
   
   GetIndString(work, itsMenuNames, kShowAuxWindow);
   gBartender->FindMenuItem(cmdToggleClip,
 &MENUid, &macMenu, &itemNo);
   if (macMenu != NULL && work[0] != 0) {
 gBartender->InsertMenuCmd(CmdToggleAuxWindow,
   work, MENUid, itemNo);
   }

 The above sequence creates a menu item that resides
 just below the Toggle Clipboard menu item. You can
 also just add the menu item to your resource file.
   
 showHideRes identifies a STR# resource containing:
 Show Status Window
 Hide Status Window
 
 These menu items will be added to the menu bar
 just after the Show/Hide clipboard menu command.
 
 Your application class DoCommand method should
 recognize the toggleCmd and send a Toggle message
 to the TextWindow:
 
 MyApp::DoCommand(long    theCommand)
 {
 switch (theCommand) {
 case CmdToggleAuxWindow:
 myAuxWindow->Toggle();
 break;
 ...
 }
 }
 
 Your application class must also enable the
 toggle command it its UpdateMenus method:
 
 MyApp::UpdateMenus()
 {
 inherited::UpdateMenus();
 gBartender->EnableCmd(CmdToggleAuxWindow);
 }
 
 void   Dispose(void);
 Dispose of the window. Actually, AuxWindow doesn't
 have its own Dispose method: the CDirector
 Dispose will free the visual hierarchy and other
 local structures.
 
 void   SetTitle(StringPtraString);
 Set the AuxWindow title:
 myAuxWindow->SetTitle(macSFReply->fName);

 void   Show(void);
 void   Hide(void);
 void   Toggle(void);
 Your application calls Toggle() to show or hide
 the window.  By default, the window is created
 invisibly.  Show() and Hide() explicitly set the
 window's visibility.
 
Support Classes

 void   Suspend(void);
 void   Resume(void);
 These override the default classes.  They hide
 when the application is suspended, and show it
 when the application is resumed (assuming it
 was visible, of course and hideOnSuspend was
 set when the window was created).
 
 void   Close(Booleanquitting);
 void   CloseWind(CWindow *theWindow);
 These override the standard classes so the window
 is hidden when closed, rather than deleted.
 
 
window definition

 The window is defined by resources as follows:
 
 *
 * The AuxWindow Window
 *
 Type WIND
 Text Box, 1024  ;; WIND_AuxWindow

  40 40 236 361
 InVisible GoAway
 4 ;; noGrowDocProc
 0
 
 *
 * The AuxWindow menu commands
 *
 Type STR#
 Text Box, 1024  ;; STRS_AuxWindow
 2
 Show Status Window
 Hide Status Window
 
 
Author
 Martin Minow

Copyright
 Copyright © 1991 Martin Minow. All Rights Reserved.
 Non-commercial use and distribution permitted.
 
#endif

#include <CBartender.h>
#include <CDesktop.h>
#include <Commands.h>
#include "AuxWindow.h"

extern CBartender*gBartender;
extern CBureaucrat *gGopher;
extern CDesktop  *gDesktop;
extern Boolean   gInBackground;

/*
 * Initialize the AuxWindow.  A director consists of a
 * visual hierarchy (CWindow and CPanes) along with a
 * control hierarchy. In this case, there really isn't
 * a unique control hierarchy: all of the standard "move
 * window" operations are handled by the TCL classes. All
 * we need to do here is to create the window and data
 * panes. In order to support hiding and revealing the
 * window, the application needs a Toggle Status Window
 * command and a STR# resource with two strings:
 * Show Status Window
 * Hide Status Window
 */
void
AuxWindow::IAuxWindow(
      CApplication *aSupervisor,
      short aWindowId,
      Boolean    isFloating,
 BooleanisHiddenOnSuspend,
 long   toggleCmd, /* Our command  */
 short  showHideRes) /* STR# text  */
{
 /*
  * Initialize the superclass.
  */
 CDirector::IDirector(aSupervisor);
 /*
  * Next, create and position the window.
  */
 itsWindow = new(CWindow);
 itsWindow->IWindow(
 aWindowId, /* A noGrowDocProc*/
 isFloating,/* Maybe floating */
 gDesktop,
 this);
 hideOnSuspend = isHiddenOnSuspend;
 itsToggleCmd = toggleCmd;
 itsMenuNames = showHideRes;
 windowVisible = FALSE;
#if 0
 /*
  * Your sub-class may want to position the
  * window and create it visibly.  If so,
  * add the following (or whatever is appropriate)
  * to your initialization method.
  */
 gDecorator->CenterWindow(itsWindow);
 Show();
#endif
}

/*
 * The SetTitle method sets the window's title bar.  Note
 * that an application that uses the AuxWindow dosen't know
 * that the title is going to a window -- or even that
 * there is a window. I.e. you could easily turn the
 * AuxWindow class into a printing or file-logging class.
 */
void
AuxWindow::SetTitle(StringPtr aTitle)
{
 itsWindow->SetTitle(aTitle);
}

/*
 * The Suspend, Resume, Close, and CloseWind methods
 * override their CDirector counterparts in order to
 * permit hiding and revealing the AuxWindow window
 * without destroying it. Your application (or some other
 * class) must respond to the CmdToggle command by calling
 * the Toggle() method. The original idea for this was
 * taken from the TCL CClipboard class.
 */
void
AuxWindow::Suspend()
{
 if (windowVisible) {
 if (hideOnSuspend == FALSE)
 itsWindow->HideSuspend();
 if (active) {
 itsWindow->Deactivate();
 active = TRUE;
 }
 }
}

void
AuxWindow::Resume()
{
 if (windowVisible) {
 if (hideOnSuspend == FALSE)
 itsWindow->ShowResume();
 if (active) {
 itsWindow->Activate();
 gGopher = this;
 }
 }
}

/*
 * Close the AuxWindow by hiding it. We don't care if the
 * application is quitting or not.
 */
Boolean
AuxWindow::Close(Boolean  quitting)
{
 if (windowVisible) {
 if (gInBackground)
 itsWindow->ShowResume();
 CloseWind(itsWindow);
 }
 return (TRUE);
}

/*
 * The user (probably) clicked in the close box --
 * or we're here because visiblity was toggled.
 * just close the window and fix the menu.
 */
void
AuxWindow::CloseWind(CWindow*theWindow)
{
 Str255 work;
 
 theWindow->Hide();
 windowVisible = FALSE;
 Deactivate();
 if (itsToggleCmd != cmdNull) {
 GetIndString(work, itsMenuNames, kShowAuxWindow);
 gBartender->SetCmdText(itsToggleCmd, work);
 }
}

void
AuxWindow::Hide()
{
 if (windowVisible)
 CloseWind(itsWindow);
}

void
AuxWindow::Show()
{
 Str255 work;
 
 if (windowVisible == FALSE) {
 itsWindow->Select();
 windowVisible = TRUE;
 if (itsToggleCmd != cmdNull) {
 GetIndString(work, itsMenuNames, kHideAuxWindow);
 gBartender->SetCmdText(itsToggleCmd, work);
 }
 /*
  * If the application enabled floating windows,
  * this window will be created "deactivated"
  * because an unwanted deactivate event is
  * stuffed -- somehow -- into the event queue. 
  * I don't really understand this, so don't be
  * suprised if it turns out to be a bug.
  */
 if (CurDeactive == itsWindow->GetMacPort())
 CurDeactive = NULL;
 }
}

/*
 * Toggle the window visiblity.  If it was invisible (or
 * newly created), set the menu command.
 */
void
AuxWindow::Toggle()
{
 if (windowVisible)
 Hide();
 else {
 Show();
 }
}
Listing: TextPane.h
/*
 * TextBox
 * Superclass: AuxWindow
 */ 
#define _H_TextPane
#include <CApplication.h>
#include <CBureaucrat.h>
#include <CPane.h>
#ifndef PRIVATE
#define PRIVATE  /* reminder */
#endif

PRIVATE struct TextPane : CPane {
 short  itsLineHeight;
 short  itsNLines;
 short  hOffset;
 short  vOffset;
 StringHandle  **vectorHandle;
 
 /*
  * Create a pane that can be used to
  * display lines of text.  The parameters
  * follow the definition of CPane. fontName
  * and fontHeight define the display font
  * and its size. These cannot be changed
  * after the class is created.
  */
 void   ITextPane(
 CView  *anEnclosure,
 CBureaucrat*aSupervisor,
 short  aWidth,
 short  aHeight,
 short  aHEncl,
 short  aVEncl,
 SizingOption  aHSizing,
 SizingOption  aVSizing,
 Str255 fontName,
 short  fontHeight);
 void   Dispose(void);
 /*
  * Write a line of text into the pane.
  */
 void   SetText(StringPtr someText);
 /*
  * Get a text item -- this should only be
  * called by DrawItem.
  */
 PRIVATE StringHandleGetText(short whichItem);
 /*
  * Draw the pane or its contents.
  */
 void   Draw(Rect*area);
 /*
  * Draw one line of text.
  */
 PRIVATE void    DrawItem(short    whichItem);
 /*
  * Scroll the text pane -- called by SetText
  * when a line of text has been added.
  */
 PRIVATE void    ScrollPane(void);
};
Listing: TextPane.c
/*
 * TextPane
 * Superclass: CPane.
 * Copyright © 1991 Martin Minow. All Rights Reserved.
 * All use and non-commercial distribution permitted.
 * Set tabs every 4 bytes.
 *
 * This is a very simple text-only, output-only, display
 * pane. It intentionally does not support cut/paste,
 * scrolling, filing and printing.
 */

#ifdef DOCUMENTATION

title   TextPane
superclassCPane
subclassesnone

usage

 #include "TextPane.h"
 
 void   ITextPane(
 CView  *anEnclosure,
 CBureaucrat*aSupervisor,
 short  aWidth,
 short  aHeight,
 short  hEncl,
 short  vEncl,
 SizingOption  hSizing,
 SizingOption  vSizing,
 Str255 fontName,
 short  fontHeight);
 Initialization: the parameters are as described in
 the CPane documentation. fontName and fontHeight
 define the font to be used to display the data.
 This cannot be changed after initialization.
 
 void   Dispose(void);
 Dispose of the TextPane and its contents.
 
 void   SetText(StringPtr someText);
 Put a line of text on the screen.
 It rolls text up the display.
 
author
 Martin Minow

copyright
 Copyright © 1991 Martin Minow. All Rights Reserved.
 Non-commercial use and distribution permitted.
 
#endif

#include <CTextEnvirons.h>
#include <TBUtilities.h>
#include "TextPane.h"

#define itsTextInfo((CTextEnvirons *) itsEnvironment)


/*
 * The TextPane acts like a "glass teletype" -- messages
 * appear at the bottom and are scrolled off the top
 * (never to appear again). Since the TextPane is private
 * and we know it will fill the window, the normal CPane
 * parameters can be constructed here rather than being
 * passed.  If this were a general-purpose pane, the full
 * CPane parameters should be passed.
 */
PRIVATE void
TextPane::ITextPane(
 CView  *anEnclosure,
 CBureaucrat*aSupervisor,
 short  aWidth,
 short  aHeight,
 short  aHEncl,
 short  aVEncl,
 SizingOption  aHSizing,
 SizingOption  aVSizing,
 Str255 fontName,
 short  fontHeight)
{
 register int    i;
 TextInfoRecaTextInfo;
 register StringHandle  *theStrings;
 FontInfo info;
 short  fontNumber;
 Rect   textBox;
 
 CPane::IPane(anEnclosure, aSupervisor, aWidth,
 aHeight, aHEncl, aVEncl, aHSizing, aVSizing);
 itsEnvironment = new(CTextEnvirons);
 itsTextInfo->ITextEnvirons();
 GetFontNumber(fontName, &fontNumber);
 /*
  * Make sure there's a drawing environment.
  */
 Prepare();
 TextFont(fontNumber);
 TextSize(fontHeight);
 GetFontInfo(&info);
 itsLineHeight = info.ascent +
 info.descent + info.leading;
 /*
  * Initialize the font stuff from the current port.
  */
 aTextInfo.fontNumber = thePort->txFont;
 aTextInfo.theStyle = thePort->txFace;
 aTextInfo.theSize = thePort->txSize;
 aTextInfo.theMode = thePort->txMode;
 itsTextInfo->SetTextInfo(&aTextInfo);
 /*
  * hOffset and vOffset define the initial position
  * of the pen when drawing text.
  */
 hOffset = info.widMax / 2;
 vOffset = info.ascent;
 /*
  * Determine the number of lines we will show.
  */
 GetAperture(&textBox);
 itsNLines = (textBox.bottom - textBox.top);
 itsNLines /= itsLineHeight;
 /*
  * Create a vector of strings. Note that this
  * limits the class -- the application cannot
  * change the font or window size after the window
  * has been created.  A "real" application would
  * display data in a CPanorama and store the strings
  * in a CList subclass.
  */
 vectorHandle = (StringHandle **)
 NewHandle(itsNLines * sizeof (StringHandle));
 /*
  * Lock the vectorHandle while we initialize
  * the string vector.  This prevents any C compiler
  * optimizations in the "theStrings[i] = ..."
  * statement from causing "memory moved" problems.
  */
 MoveHHi(vectorHandle);
 HLock(vectorHandle);
 theStrings = *vectorHandle;
 for (i = 0; i < itsNLines; i++)
 theStrings[i] = NewString((StringPtr) "\p");
 HUnlock(vectorHandle);
}

/*
 * Dispose of the TextPane by deleting the strings and
 * the string vector. This is called by TCL when the
 * enclosing window is disposed. (Note also that TCL
 * will dispose of the environment object, too.)
 */
void
TextPane::Dispose()
{
 register int    i;
 register StringHandle  *theStrings;
 
 HLock(vectorHandle);
 theStrings = *vectorHandle;
 for (i = 0; i < itsNLines; i++)
 DisposHandle(theStrings[i]);
 DisposHandle(vectorHandle);
 inherited::Dispose();
}

/*
 * The SetText method adds text to the TextPane.
 */
void
TextPane::SetText(StringPtr someText)
{
 register StringHandle  *theStrings;
 StringHandle    tempHandle;
 register int    i;
 
 theStrings = (StringHandle *) *vectorHandle;
 /*
  * Save the top string's handle for recycling,
  * move the others "up" in the display,
  * insert the old top string at the bottom,
  * and set its contents to the supplied value.
  * Finally, update the display.
  * Note that we must not depend on the value
  * of theStrings after calling SetString or
  * ScrollPane as either may move memory.
  *
  * The following sequence may not move memory.
  */
 tempHandle = theStrings[0];
 for (i = 1; i < itsNLines; i++)
 theStrings[i - 1] = theStrings[i];
 theStrings[itsNLines - 1] = tempHandle;
 /*
  * The above sequence may not move memory.
  */
 SetString(tempHandle, someText);
 ScrollPane();
}

/*
 * This is the only function that accesses the text.
 */
StringHandle
TextPane::GetText(short   whichItem)
{
 return ((*vectorHandle)[whichItem]);
}

/*
 * Draw is called by the TCL when part of the TextPane
 * has been uncovered.  Note that the actual drawing
 * is done by a DrawItem method -- this lets us have
 * one common routine for both Draw and ScrollPane.
 * This is overkill for this demo application, but useful
 * when the thing being drawn is complex.
 */
void
TextPane::Draw(Rect*area)
{
 register int    i;

 for (i = 0; i < itsNLines; i++)
 DrawItem(i);
}

/*
 * This method scrolls the text pane up one line and draws
 * the last line (which has just been inserted).
 */
PRIVATE void
TextPane::ScrollPane()
{
 Rect   theView;
 RgnHandletempRgn;
 
 Prepare();
 tempRgn = NewRgn();
 GetAperture(&theView);
 ScrollRect(&theView, 0, -itsLineHeight, tempRgn);
 DrawItem(itsNLines - 1);
 ValidRgn(tempRgn);
 DisposeRgn(tempRgn);
}

/*
 * This is the only function that draws the data.
 */
PRIVATE void
TextPane::DrawItem(short  whichItem)
{
 register StringHandle  theString;
 SignedByte saveHState;
 
 theString = GetText(whichItem);
 saveHState = HGetState(theString);
 HLock(theString);
 MoveTo(hOffset,
 vOffset + (whichItem * itsLineHeight));
 DrawString(*theString);
 HSetState(theString, saveHState);
}
Listing: TextWindow.h
/*
 * TextWindow
 * Superclass: AuxWindow
 */ 
#define _H_TextBox
#include <CApplication.h>
#include <CPane.h>
#include "AuxWindow.h"
#include "TextPane.h"

 struct TextWindow : AuxWindow {
 struct TextPane *itsTextPane;

 /*
  * Create a text window. The parameters
  * are passed on to AuxWindow (except
  * for fontName and fontSize that are
  * needed by the TextPane.
  */
 void   ITextWindow(
 CApplication  *aSupervisor,
 short  aWindowId,
 BooleanisFloating,
 BooleanhideOnSuspend,
 long   toggleCmd,
 short  showHideRes,
 StringPtrfontName,
 short  fontSize);
 /*
  * Write a message into the TextPane.
  */
 void   SetText(StringPtr someText);
};
Listing: TextWindow.c
/*
 * TextWindow
 * Superclass: CDirector.
 * Copyright © 1991 Martin Minow. All Rights Reserved.
 * All use and non-commercial distribution permitted.
 * Set tabs every 4 bytes.
 *
 * This is a very simple text-only, output-only, display
 * window. It intentionally does not support cut/paste,
 * filing and printing.  It is based on the design of
 * the TCL CClipboard class.  In fact, if I was cleverer,
 * I'd make this a sub-class of CClipboard.
 */

#ifdef DOCUMENTATION

title   TextWindow
superclassCDirector
subclassesnone

usage

 #include "TextWindow.h"
 
 void   ITextWindow(
 CApplication  *aSupervisor,
 short  aWindowId,
 BooleanisFloating,
 BooleanhideOnSuspend,
 long   toggleCmd,
 short  showHideRes,
 StringPtrfontName,
 short  fontSize);
 Initialization.  itsSupervisor must be the
 application.  It displays the window defined
 by aWindowId. Text is written in the indicated
 font and size.  The toggleCmd and showHideRes
 parameters are described in the AuxWindow class.
 
 void   Dispose(void);
 Dispose of the window and its contents.
 
 void   SetText(StringPtr someText);
 Put a line of text on the screen.
 It rolls text up the display.
 
 StringHandle    GetText(shortindex);
 Get the StringHandle for the specified line.      
 
author
 Martin Minow

copyright
 Copyright © 1991 Martin Minow. All Rights Reserved.
 Non-commercial use and distribution permitted.
 
#endif

#include <CBureaucrat.h>
#include <CDecorator.h>
#include <CTextEnvirons.h>
#include <TBUtilities.h>
#include "TextWindow.h"

extern CBureaucrat *gGopher;
extern CDecorator*gDecorator;

#define itsTextInfo((CTextEnvirons *) itsEnvironment)

/*
 * Initialize the TextWindow.
 */
void
TextWindow::ITextWindow(
      CApplication *aSupervisor,   /* The app.     */
      short aWindowId,    /* WIND res. */
      Boolean    isFloating,/* Floating?     */
      Boolean    hideOnSuspend,  /* Mac-like?      */
 long   toggleCmd, /* Show/hide it */
 short  showHideRes, /* STR# text  */
      StringPtr  fontName,/* Font name */
      short fontSize)/* Font size  */
{
 Rect   windowBox;
 
 AuxWindow::IAuxWindow(   /* Init superclass */
 aSupervisor,    /* Supervisor*/
 aWindowId, /* Enclosure  */
 isFloating,/* Does it float? */
 hideOnSuspend,  /* Mac-like? */
 toggleCmd, /* Our command*/
 showHideRes);   /* Menu text STR# */
 gDecorator->CenterWindow(itsWindow);
 itsWindow->GetAperture(&windowBox);
 itsTextPane = new(TextPane);
 itsTextPane->ITextPane(
 itsWindow, /* Enclosure  */
 this,  /* Enclosure */
 windowBox.right - windowBox.left, /* Width  */
 windowBox.bottom - windowBox.top, /* Height */
 0, 0,  /* Offset in enclosure*/
 sizFIXEDLEFT, sizFIXEDTOP,
 fontName,
 fontSize);
}

/*
 * The SetText method adds text to the data pane.
 */
void
TextWindow::SetText(StringPtr someText)
{
 itsTextPane->SetText(someText);
}

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Minecraft 1.20.2 - Popular sandbox build...
Minecraft allows players to build constructions out of textured cubes in a 3D procedurally generated world. Other activities in the game include exploration, gathering resources, crafting, and combat... Read more
HoudahSpot 6.4.1 - Advanced file-search...
HoudahSpot is a versatile desktop search tool. Use HoudahSpot to locate hard-to-find files and keep frequently used files within reach. HoudahSpot is a productivity tool. It is the hub where all the... Read more
coconutBattery 3.9.14 - Displays info ab...
With coconutBattery you're always aware of your current battery health. It shows you live information about your battery such as how often it was charged and how is the current maximum capacity in... Read more
Keynote 13.2 - Apple's presentation...
Easily create gorgeous presentations with the all-new Keynote, featuring powerful yet easy-to-use tools and dazzling effects that will make you a very hard act to follow. The Theme Chooser lets you... Read more
Apple Pages 13.2 - Apple's word pro...
Apple Pages is a powerful word processor that gives you everything you need to create documents that look beautiful. And read beautifully. It lets you work seamlessly between Mac and iOS devices, and... Read more
Numbers 13.2 - Apple's spreadsheet...
With Apple Numbers, sophisticated spreadsheets are just the start. The whole sheet is your canvas. Just add dramatic interactive charts, tables, and images that paint a revealing picture of your data... Read more
Ableton Live 11.3.11 - Record music usin...
Ableton Live lets you create and record music on your Mac. Use digital instruments, pre-recorded sounds, and sampled loops to arrange, produce, and perform your music like never before. Ableton Live... Read more
Affinity Photo 2.2.0 - Digital editing f...
Affinity Photo - redefines the boundaries for professional photo editing software for the Mac. With a meticulous focus on workflow it offers sophisticated tools for enhancing, editing and retouching... Read more
SpamSieve 3.0 - Robust spam filter for m...
SpamSieve is a robust spam filter for major email clients that uses powerful Bayesian spam filtering. SpamSieve understands what your spam looks like in order to block it all, but also learns what... Read more
WhatsApp 2.2338.12 - Desktop client for...
WhatsApp is the desktop client for WhatsApp Messenger, a cross-platform mobile messaging app which allows you to exchange messages without having to pay for SMS. WhatsApp Messenger is available for... Read more

Latest Forum Discussions

See All

‘Resident Evil 4’ Remake Pre-Orders Are...
Over the weekend, Capcom revealed the Japanese price points for both upcoming iOS and iPadOS ports of Resident Evil Village and Resident Evil 4 Remake , in addition to confirming the release date for Resident Evil Village. Since then, pre-orders... | Read more »
Square Enix commemorates one of its grea...
One of the most criminally underused properties in the Square Enix roster is undoubtedly Parasite Eve, a fantastic fusion of Resident Evil and Final Fantasy that deserved far more than two PlayStation One Games and a PSP follow-up. Now, however,... | Read more »
Resident Evil Village for iPhone 15 Pro...
During its TGS 2023 stream, Capcom showcased the Following upcoming ports revealed during the Apple iPhone 15 event. Capcom also announced pricing for the mobile (and macOS in the case of the former) ports of Resident Evil 4 Remake and Resident Evil... | Read more »
The iPhone 15 Episode – The TouchArcade...
After a 3 week hiatus The TouchArcade Show returns with another action-packed episode! Well, maybe not so much “action-packed" as it is “packed with talk about the iPhone 15 Pro". Eli, being in a time zone 3 hours ahead of me, as well as being smart... | Read more »
TouchArcade Game of the Week: ‘DERE Veng...
Developer Appsir Games have been putting out genre-defying titles on mobile (and other platforms) for a number of years now, and this week marks the release of their magnum opus DERE Vengeance which has been many years in the making. In fact, if the... | Read more »
SwitchArcade Round-Up: Reviews Featuring...
Hello gentle readers, and welcome to the SwitchArcade Round-Up for September 22nd, 2023. I’ve had a good night’s sleep, and though my body aches down to the last bit of sinew and meat, I’m at least thinking straight again. We’ve got a lot to look at... | Read more »
TGS 2023: Level-5 Celebrates 25 Years Wi...
Back when I first started covering the Tokyo Game Show for TouchArcade, prolific RPG producer Level-5 could always be counted on for a fairly big booth with a blend of mobile and console games on offer. At recent shows, the company’s presence has... | Read more »
TGS 2023: ‘Final Fantasy’ & ‘Dragon...
Square Enix usually has one of the bigger, more attention-grabbing booths at the Tokyo Game Show, and this year was no different in that sense. The line-ups to play pretty much anything there were among the lengthiest of the show, and there were... | Read more »
Valve Says To Not Expect a Faster Steam...
With the big 20% off discount for the Steam Deck available to celebrate Steam’s 20th anniversary, Valve had a good presence at TGS 2023 with interviews and more. | Read more »
‘Honkai Impact 3rd Part 2’ Revealed at T...
At TGS 2023, HoYoverse had a big presence with new trailers for the usual suspects, but I didn’t expect a big announcement for Honkai Impact 3rd (Free). | Read more »

Price Scanner via MacPrices.net

New low price: 13″ M2 MacBook Pro for $1049,...
Amazon has the Space Gray 13″ MacBook Pro with an Apple M2 CPU and 256GB of storage in stock and on sale today for $250 off MSRP. Their price is the lowest we’ve seen for this configuration from any... Read more
Apple AirPods 2 with USB-C now in stock and o...
Amazon has Apple’s 2023 AirPods Pro with USB-C now in stock and on sale for $199.99 including free shipping. Their price is $50 off MSRP, and it’s currently the lowest price available for new AirPods... Read more
New low prices: Apple’s 15″ M2 MacBook Airs w...
Amazon has 15″ MacBook Airs with M2 CPUs and 512GB of storage in stock and on sale for $1249 shipped. That’s $250 off Apple’s MSRP, and it’s the lowest price available for these M2-powered MacBook... Read more
New low price: Clearance 16″ Apple MacBook Pr...
B&H Photo has clearance 16″ M1 Max MacBook Pros, 10-core CPU/32-core GPU/1TB SSD/Space Gray or Silver, in stock today for $2399 including free 1-2 day delivery to most US addresses. Their price... Read more
Switch to Red Pocket Mobile and get a new iPh...
Red Pocket Mobile has new Apple iPhone 15 and 15 Pro models on sale for $300 off MSRP when you switch and open up a new line of service. Red Pocket Mobile is a nationwide service using all the major... Read more
Apple continues to offer a $350 discount on 2...
Apple has Studio Display models available in their Certified Refurbished store for up to $350 off MSRP. Each display comes with Apple’s one-year warranty, with new glass and a case, and ships free.... Read more
Apple’s 16-inch MacBook Pros with M2 Pro CPUs...
Amazon is offering a $250 discount on new Apple 16-inch M2 Pro MacBook Pros for a limited time. Their prices are currently the lowest available for these models from any Apple retailer: – 16″ MacBook... Read more
Closeout Sale: Apple Watch Ultra with Green A...
Adorama haș the Apple Watch Ultra with a Green Alpine Loop on clearance sale for $699 including free shipping. Their price is $100 off original MSRP, and it’s the lowest price we’ve seen for an Apple... Read more
Use this promo code at Verizon to take $150 o...
Verizon is offering a $150 discount on cellular-capable Apple Watch Series 9 and Ultra 2 models for a limited time. Use code WATCH150 at checkout to take advantage of this offer. The fine print: “Up... Read more
New low price: Apple’s 10th generation iPads...
B&H Photo has the 10th generation 64GB WiFi iPad (Blue and Silver colors) in stock and on sale for $379 for a limited time. B&H’s price is $70 off Apple’s MSRP, and it’s the lowest price... Read more

Jobs Board

Housekeeper, *Apple* Valley Villa - Cassia...
Apple Valley Villa, part of a 4-star senior living community, is hiring entry-level Full-Time Housekeepers to join our team! We will train you for this position and Read more
Housekeeper, *Apple* Valley Village - Cassi...
Apple Valley Village Health Care Center, a 4-star rated senior care campus, is hiring a Part-Time Housekeeper to join our team! We will train you for this position! Read more
Optometrist- *Apple* Valley, CA- Target Opt...
Optometrist- Apple Valley, CA- Target Optical Date: Sep 23, 2023 Brand: Target Optical Location: Apple Valley, CA, US, 92308 **Requisition ID:** 796045 At Target Read more
Senior *Apple* iOS CNO Developer (Onsite) -...
…Offense and Defense Experts (CODEX) is in need of smart, motivated and self-driven Apple iOS CNO Developers to join our team to solve real-time cyber challenges. Read more
*Apple* Systems Administrator - JAMF - Activ...
…**Public Trust/Other Required:** None **Job Family:** Systems Administration **Skills:** Apple Platforms,Computer Servers,Jamf Pro **Experience:** 3 + years of Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.