main.c

/*
*   File:       main.c of TypeServicesForUnicode
* 
*   Contains:   This is a sample program that demonstrates how to use
*                   the Apple Text Services For Unicode Imaging (ATSUI) technology
*                   in a Carbon Events based HIView architectured application.
*                   
*                   It contains a ready-to-use HIATSUIView which is a custom HIView
*
*                   The HIATSUIView handles:
*                       - getting and setting the text content
*                       - getting and setting the styles
*                       - getting and setting the selection
*                       - various features like colors, wrapping, framing, justification, and transparency
*                       - copying and dragging
*                       - scrolling and auto-scrolling
*                       - handling of   double-clicks, triple-clicks, quadruple=clicks,
*                                           click-and-drag, shift-clicking
*
*                   The TypeForServices code handles:
*                       - showing how to set the text
*                       - showing how to set the text and styles
*                       - showing how to rotate
*                       - showing how to handle font variants
*                       - showing how to handle font selection through the standard Font menu
*                       - showing how to handle font selection through the standard Font panel
*                       - showing how to instantiate the HIATSUIView from a nib
*                       - showing how to read a text file and set the HIATSUIView content
*                       - showing how to handle the clipboard
*                       - showing how to handle background pictures
*
*  Note:        The project is set up so that the DEBUG macro is set to one when the "Development"
*               build style is chosen and not at all when the "Deployment" build style is chosen.
*               Thus, all the require asserts "fire" only in "Development".
*   
*   Version:    2.0
* 
*   Created:    11/4/04
*
*   Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple Computer, Inc.
*               ("Apple") in consideration of your agreement to the following terms, and your
*               use, installation, modification or redistribution of this Apple software
*               constitutes acceptance of these terms.  If you do not agree with these terms,
*               please do not use, install, modify or redistribute this Apple software.
*
*               In consideration of your agreement to abide by the following terms, and subject
*               to these terms, Apple grants you a personal, non-exclusive license, under AppleÍs
*               copyrights in this original Apple software (the "Apple Software"), to use,
*               reproduce, modify and redistribute the Apple Software, with or without
*               modifications, in source and/or binary forms; provided that if you redistribute
*               the Apple Software in its entirety and without modifications, you must retain
*               this notice and the following text and disclaimers in all such redistributions of
*               the Apple Software.  Neither the name, trademarks, service marks or logos of
*               Apple Computer, Inc. may be used to endorse or promote products derived from the
*               Apple Software without specific prior written permission from Apple.  Except as
*               expressly stated in this notice, no other rights or licenses, express or implied,
*               are granted by Apple herein, including but not limited to any patent rights that
*               may be infringed by your derivative works or by other works in which the Apple
*               Software may be incorporated.
*
*               The Apple Software is provided by Apple on an "AS IS" basis.  APPLE MAKES NO
*               WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED
*               WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR
*               PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN
*               COMBINATION WITH YOUR PRODUCTS.
*
*               IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR
*               CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
*               GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
*               ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION
*               OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT
*               (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN
*               ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*   Copyright:  Copyright © 2005 Apple Computer, Inc, All Rights Reserved
*/
//****************************************************
#pragma mark * compilation directives *
 
//****************************************************
#pragma mark -
#pragma mark * includes & imports *
 
#include <Carbon/Carbon.h>
#include "TypeServicesForUnicode.h"
 
//****************************************************
#pragma mark -
#pragma mark * typedef's, struct's, enums, defines, etc. *
 
//****************************************************
#pragma mark -
#pragma mark * local (static) function prototypes *
 
static pascal OSErr Handle_OpenApplication(const AppleEvent *inAppleEvent, AppleEvent *outAppleEvent, long inHandlerRefcon);
static pascal OSErr Handle_ReopenApplication(const AppleEvent *inAppleEvent, AppleEvent *outAppleEvent, long inHandlerRefcon);
static pascal OSErr Handle_OpenDocuments(const AppleEvent *inAppleEvent, AppleEvent *outAppleEvent, long inHandlerRefcon);
static pascal OSErr Handle_PrintDocuments(const AppleEvent *inAppleEvent, AppleEvent *outAppleEvent, long inHandlerRefcon);
static void Install_AppleEventHandlers(void);
 
static pascal OSStatus Handle_CommandUpdateStatus(EventHandlerCallRef inHandlerCallRef, EventRef inEvent, void *inUserData);
static pascal OSStatus Handle_CommandProcess(EventHandlerCallRef inHandlerCallRef, EventRef inEvent, void *inUserData);
 
static void Do_Preferences(void);
 
static OSStatus Do_CleanUp(void);
 
//****************************************************
#pragma mark -
#pragma mark * exported globals *
 
//****************************************************
#pragma mark -
#pragma mark * local (static) globals *
 
static IBNibRef gIBNibRef;
 
//****************************************************
#pragma mark -
#pragma mark * exported function implementations *
 
/*****************************************************
*
* main (argc, argv) 
*
* Purpose:  main program entry point
*
* Notes:       You might want to change this to something more verbose
*
* Inputs:   argc     - the number of elements in the argv array
*               argv     - an array of pointers to the parameters to this application
*
* Returns:  int      - error code (0 == no error) 
*/
int main(int argc, char* argv[])
    {
    OSStatus status;
    
    // Can we run this particular demo application?
    long response;
    status = Gestalt(gestaltSystemVersion, &response);
    Boolean ok = ((noErr == status) && (response >= 0x00001030));
    if (!ok)
        {
        DialogRef theAlert;
        CreateStandardAlert(kAlertStopAlert, CFSTR("Mac OS X 10.3 (minimum) is required for this application"), NULL, NULL, &theAlert);
        RunStandardAlert(theAlert, NULL, NULL);
        ExitToShell();
        }
    
    // Create a Nib reference passing the name of the nib file (without the .nib extension)
    // CreateNibReference only searches into the application bundle.
    status = CreateNibReference(CFSTR("main"), &gIBNibRef);
    require_noerr(status, CantGetNibRef);
    
    // Once the nib reference is created, set the menu bar. "MainMenu" is the name of the menu bar
    // object. This name is set in InterfaceBuilder when the nib is created.
    status = SetMenuBarFromNib(gIBNibRef, CFSTR("MenuBar"));
    require_noerr(status, CantSetMenuBar);
    
    // Adding a Font menu
    MenuRef fontMenu = GetMenuRef(3);
    require(fontMenu != NULL, CantSetMenuBar);
    status = CreateStandardFontMenu(fontMenu, 0, 0, 0, NULL);
    require_noerr(status, CantSetMenuBar);
    
    // Enabling Preferences menu item
    EnableMenuCommand(NULL, kHICommandPreferences);
    
    // Let's react to User's commands.
    Install_AppleEventHandlers();
    
    EventTypeSpec eventTypeCP = {kEventClassCommand, kEventCommandProcess};
    InstallEventHandler(GetApplicationEventTarget(), Handle_CommandProcess, 1, &eventTypeCP, NULL, NULL);
    
    EventTypeSpec eventTypeCUS = {kEventClassCommand, kEventCommandUpdateStatus};
    InstallEventHandler(GetApplicationEventTarget(), Handle_CommandUpdateStatus, 1, &eventTypeCUS, NULL, NULL);
    
    EventTypeSpec eventTypeAA = {kEventClassApplication,  kEventAppActivated};
    InstallEventHandler(GetApplicationEventTarget(), Handle_AppActivated, 1, &eventTypeAA, NULL, NULL);
    
    // Call the event loop
    RunApplicationEventLoop();
    
CantSetMenuBar:
CantGetNibRef:
    return status;
    }   // main
 
/*****************************************************/
#pragma mark -
#pragma mark * local (static) function implementations *
#pragma mark * AppleEvent Handlers *
 
/*****************************************************
*
* Handle_OpenApplication(inAppleEvent, reply, inHandlerRefcon) 
*
* Purpose:  AppleEvent handler for the kAEOpenApplication event
*
* Inputs:   inAppleEvent       - the Apple event
*           reply              - our reply to the Apple event
*           inHandlerRefcon    - refcon passed to AEInstallEventHandler when this hander was installed
*
* Returns:  OSErr              - error code (0 == no error) 
*/
static pascal OSErr Handle_OpenApplication(const AppleEvent *inAppleEvent, AppleEvent *outAppleEvent, long inHandlerRefcon)
    {
    return Do_NewSingleLineSingleStyleWindow(); // create an empty window
    }   // Handle_OpenApplication
 
/*****************************************************
*
* Handle_ReopenApplication(inAppleEvent, reply, inHandlerRefcon) 
*
* Purpose:  AppleEvent handler for the kAEReopenApplication event
*
* Inputs:   inAppleEvent       - the Apple event
*           reply              - our reply to the Apple event
*           inHandlerRefcon    - refcon passed to AEInstallEventHandler when this hander was installed
*
* Returns:  OSErr              - error code (0 == no error) 
*/
static pascal OSErr Handle_ReopenApplication(const AppleEvent *inAppleEvent, AppleEvent *outAppleEvent, long inHandlerRefcon)
    {
    // We were already running but with no windows so we create an empty one.
    WindowRef theWindow = GetFrontWindowOfClass(kDocumentWindowClass, true);
    if (theWindow == NULL)
        return Do_NewSingleLineSingleStyleWindow();
    else
        return noErr;
    }   // Handle_ReopenApplication
 
/*****************************************************
*
* Handle_OpenDocuments(inAppleEvent, reply, inHandlerRefcon) 
*
* Purpose:  AppleEvent handler for the kAEOpenDocuments event
*
* Inputs:   inAppleEvent       - the Apple event
*           reply              - our reply to the Apple event
*           inHandlerRefcon    - refcon passed to AEInstallEventHandler when this hander was installed
*
* Returns:  OSErr              - error code (0 == no error) 
*/
static pascal OSErr Handle_OpenDocuments(const AppleEvent *inAppleEvent, AppleEvent *outAppleEvent, long inHandlerRefcon)
    {
    return errAEEventNotHandled;
    }   // Handle_OpenDocuments
 
/*****************************************************
*
* Handle_PrintDocuments(inAppleEvent, reply, inHandlerRefcon) 
*
* Purpose:  AppleEvent handler for the kAEPrintDocuments event
*
* Inputs:   inAppleEvent       - the Apple event
*           reply              - our reply to the Apple event
*           inHandlerRefcon    - refcon passed to AEInstallEventHandler when this hander was installed
*
* Returns:  OSErr              - error code (0 == no error) 
*/
static pascal OSErr Handle_PrintDocuments(const AppleEvent *inAppleEvent, AppleEvent *outAppleEvent, long inHandlerRefcon)
    {
    return errAEEventNotHandled;
    }   // Handle_PrintDocuments
 
/*****************************************************
*
* Install_AppleEventHandlers(void) 
*
* Purpose:  installs the AppleEvent handlers
*
* Inputs:   none
*
* Returns:  none
*/
static void Install_AppleEventHandlers(void)
    {
    OSErr   status;
    status = AEInstallEventHandler(kCoreEventClass, kAEOpenApplication, Handle_OpenApplication, 0, false);
    require_noerr(status, CantInstallAppleEventHandlerOpenAppl);
    
    status = AEInstallEventHandler(kCoreEventClass, kAEReopenApplication, Handle_ReopenApplication, 0, false);
    require_noerr(status, CantInstallAppleEventHandlerReOpenAppl);
    
    status = AEInstallEventHandler(kCoreEventClass, kAEOpenDocuments, Handle_OpenDocuments, 0, false);
    require_noerr(status, CantInstallAppleEventHandlerOpenDocs);
    
    status = AEInstallEventHandler(kCoreEventClass, kAEPrintDocuments, Handle_PrintDocuments, 0, false);
    require_noerr(status, CantInstallAppleEventHandlerPrintDocs);
    
    // Note: Since RunApplicationEventLoop installs a Quit AE Handler, there is no need to do it here.
    
CantInstallAppleEventHandlerOpenAppl:
CantInstallAppleEventHandlerReOpenAppl:
CantInstallAppleEventHandlerOpenDocs:
CantInstallAppleEventHandlerPrintDocs:
    return;
    }   // Install_AppleEventHandlers
 
#pragma mark -
#pragma mark * CarbonEvent Handlers *
 
/*****************************************************
*
* Handle_CommandUpdateStatus(inHandlerCallRef, inEvent, inUserData) 
*
* Purpose:  called to update status of the commands, enabling or disabling the menu items
*
* Inputs:   inHandlerCallRef    - reference to the current handler call chain
*               inEvent             - the event
*           inUserData          - app-specified data you passed in the call to InstallEventHandler
*
* Returns:  OSStatus            - noErr indicates the event was handled
*                                 eventNotHandledErr indicates the event was not handled and the Toolbox should take over
*/
static pascal OSStatus Handle_CommandUpdateStatus(EventHandlerCallRef inHandlerCallRef, EventRef inEvent, void *inUserData)
    {
    OSStatus status = eventNotHandledErr;
    
    HICommand aCommand;
    GetEventParameter(inEvent, kEventParamDirectObject, typeHICommand, NULL, sizeof(HICommand), NULL, &aCommand);
    
    // Do we have a front window containing a HIATSUIView?
    WindowRef frontWindow;
    HIViewRef atsuiView;
    Get_FrontWindowAndATSUIView(&frontWindow, &atsuiView, false);
    
    // Do we have some selected text?
    Boolean textSelected = false;
    if (atsuiView != NULL)
        {
        UniCharArrayOffset start, end;
        HIATSUIViewGetSelection(atsuiView, &start, &end);
        textSelected = (start != end);
        }
 
    if (frontWindow == NULL)
        {
        // no window, let's disable all relevant menu items
        switch (aCommand.commandID)
            {
            case kHICommandSelectAll:
            case kHICommandCopy:
            case kHICommandClose:
                DisableMenuItem(aCommand.menu.menuRef, aCommand.menu.menuItemIndex);
                break;
            }
        }
    else
        {
        switch (aCommand.commandID)
            {
            // the "Copy" menu item is enabled only is some text is selected
            case kHICommandCopy:
                if (textSelected)
                    EnableMenuItem(aCommand.menu.menuRef, aCommand.menu.menuItemIndex);
                else
                    DisableMenuItem(aCommand.menu.menuRef, aCommand.menu.menuItemIndex);
                break;
            
            // the "Select All" menu item is enabled only if there is a HIATSUIView in the front window
            case kHICommandSelectAll:
                if (atsuiView != NULL)
                    EnableMenuItem(aCommand.menu.menuRef, aCommand.menu.menuItemIndex);
                else
                    DisableMenuItem(aCommand.menu.menuRef, aCommand.menu.menuItemIndex);
                break;
            
            // the "Close" menu item is enabled only if there is a window
            case kHICommandClose:
                EnableMenuItem(aCommand.menu.menuRef, aCommand.menu.menuItemIndex);
                break;
            }
        }
    
    // let's adjust the "Show/Hide Fonts" menu item title depending on the visibility of the Standard Font panel
    if (aCommand.commandID == kHICommandShowHideFontPanel)
        {
        CFStringRef menuItemString;
        if (FPIsFontPanelVisible())
            menuItemString = CFCopyLocalizedString( CFSTR("Hide Fonts"), CFSTR("") );
        else
            menuItemString = CFCopyLocalizedString( CFSTR("Show Fonts"), CFSTR("") );
        SetMenuItemTextWithCFString(aCommand.menu.menuRef, aCommand.menu.menuItemIndex, menuItemString);
        CFRelease(menuItemString);
        }
 
    return status;
    }   // Handle_CommandUpdateStatus
 
/*****************************************************
*
* Handle_CommandProcess(inHandlerCallRef, inEvent, inUserData) 
*
* Purpose:  called to process commands from Carbon events
*
* Inputs:   inHandlerCallRef    - reference to the current handler call chain
*              inEvent             - the event
*           inUserData          - app-specified data you passed in the call to InstallEventHandler
*
* Returns:  OSStatus            - noErr indicates the event was handled
*                                 eventNotHandledErr indicates the event was not handled and the Toolbox should take over
*/
static pascal OSStatus Handle_CommandProcess(EventHandlerCallRef inHandlerCallRef, EventRef inEvent, void *inUserData)
    {
    HICommand aCommand;
    OSStatus status = eventNotHandledErr;
    
    GetEventParameter(inEvent, kEventParamDirectObject, typeHICommand, NULL, sizeof(HICommand), NULL, &aCommand);
    
    switch (aCommand.commandID)
        {
        case kHICommandPreferences:
            Do_Preferences();
            break;
        case kHICommandOpen:
            status = Do_OpenATextFile();
            break;
        case kHICommandSelectAll:
            status = Handle_SelectAll();
            break;
        case kHICommandCopy:
            status = Handle_Copy();
            break;
        case 'SLSS':
            status = Do_NewSingleLineSingleStyleWindow();
            break;
        case 'SLMS':
            status = Do_NewSingleLineMultipleStyleWindow();
            break;
        case 'VLMS':
            status = Do_NewSingleVerticalLineMultipleStyleWindow();
            break;
        case 'PRGR':
            status = Do_NewParagraphsWindow();
            break;
        case 'HGHL':
            status = Do_NewHighlightingWindow();
            break;
        case 'HTST':
            status = Do_NewHitTestingWindow();
            break;
        case 'UCLP':
            status = Do_ShowClipboardWindow();
            break;
        case 'TXTB':
            status = Do_NewTextBoxWindow(gIBNibRef);
            break;
        case 'FNTV':
            status = Do_NewUsingFontVariantsWindow();
            break;
        case 'SCRL':
            status = Do_NewScrollingWindow();
            break;
        case 'UAPI':
            status = Do_CreateWithTextWindow();
            break;
        case 'UNIB':
            status = Do_CreateFromNibWindow(gIBNibRef);
            break;
        case kHICommandShowHideFontPanel:
            // nothing to do, we just pass through, returning eventNotHandledErr so that the HIToolbox
            // shows or hides the System Font Panel
            break;
        case kHICommandAbout:
            // nothing to do, we just pass through, returning eventNotHandledErr so that the HIToolbox
            // shows the Standard About Box
            break;
        case kHICommandQuit:
            status = Do_CleanUp();
            break;
        default:
            {
            // we may have a font selection from the Font Menu.
            status = Handle_FontMenuSelection(&aCommand);
            
            // if not, let's print out the unknown command for debugging purposes
            if (status != noErr)
                {
                char buffer[200];
                char smallbuf[4], *p = smallbuf;
                *((UInt32 *)p) = aCommand.commandID;
                sprintf(buffer, "Got unknown command id: %ld, '%c%c%c%c', ", aCommand.commandID, p[0], p[1], p[2], p[3]);
                debug_string(buffer);
                }
            }
            break;
        }
    return status;
    }   // Handle_CommandProcess
 
#pragma mark -
#pragma mark * Windows *
 
/*****************************************************
*
* Do_Preferences(void) 
*
* Purpose:  routine to display dialog to set our applications preferences
*
* Inputs:   none
*
* Returns:  none
*/
static void Do_Preferences(void)
    {
    DialogRef theAlert;
    CreateStandardAlert(kAlertStopAlert, CFSTR("No Preferences yet!"), NULL, NULL, &theAlert);
    RunStandardAlert(theAlert, NULL, NULL);
    }   // Do_Preferences
 
/*****************************************************
*
* Do_CleanUp(void) 
*
* Purpose:  called when we get the quit event, closes all the windows.
*
* Inputs:   none
*
* Returns:  OSStatus   - eventNotHandledErr indicates that the quit process can continue
*/
static OSStatus Do_CleanUp(void)
    {
    WindowRef windowToDispose, aWindowRef = GetFrontWindowOfClass(kDocumentWindowClass, true);
 
    for ( ; aWindowRef != NULL; )
        {
        windowToDispose = aWindowRef;
        aWindowRef = GetNextWindowOfClass(aWindowRef, kDocumentWindowClass, true);
        DisposeWindow(windowToDispose);
        }
    
    return eventNotHandledErr;
    }   // Do_CleanUp