LoginItemsAETest/LoginItemsAETest.c

/*
    File:       LoginItemsAETest.c
 
    Contains:   Test program for LoginItemsAE module.
 
    Copyright:  Copyright © 2005 by Apple Computer, Inc., All Rights Reserved.
 
    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.
 
    Change History (most recent first):
 
$Log: LoginItemsAETest.c,v $
Revision 1.1  2005/09/27 12:29:32  eskimo1
First checked in.
 
 
*/
 
/////////////////////////////////////////////////////////////////
 
// System interfaces
 
#include <Carbon/Carbon.h>
 
#include <assert.h>
#include <stdio.h>
 
// Project interfaces
 
#include "LoginItemsAE.h"
 
/////////////////////////////////////////////////////////////////
 
static void CFQRelease(CFTypeRef cf)
{
    if (cf != NULL) {
        CFRelease(cf);
    }
}
 
static void DoAbout(void)
    // Displays the about box.
{
    SInt16 junkHit;
 
    (void) StandardAlert(
        kAlertPlainAlert, 
        "\pLoginItemsTestAE", 
        "\pA simple program to test LoginItemsAE.\r\rDTS\r\r© 2005 Apple Computer, Inc.", 
        NULL, 
        &junkHit
    );
}
 
static void DisplayError(OSStatus err)
    // Displays a trivial error dialog if err represents an error.
{
    if ( (err != noErr) && (err != userCanceledErr) ) {
        AlertStdCFStringAlertParamRec   params;
        DialogRef                       dlg;
        DialogItemIndex                 junkItem;
        
        params.version       = kStdCFStringAlertVersionOne;
        params.movable       = true;
        params.helpButton    = false;
        params.defaultText   = (CFStringRef) kAlertDefaultOKText;
        params.cancelText    = NULL;
        params.otherText     = NULL;
        params.defaultButton = kAlertStdAlertOKButton;
        params.cancelButton  = 0;
        params.position      = kWindowCenterMainScreen;
        params.flags         = 0;
        
        err = CreateStandardAlert(
            kAlertStopAlert, 
            CFSTR("LoginItemsAE got an error."), 
            CFStringCreateWithFormat(NULL, NULL, CFSTR("%ld"), err),
            &params, 
            &dlg
        );
        if (err == noErr) {
            err = RunStandardAlert(dlg, NULL, &junkItem);
        }
        assert(err == noErr);
    }
}
 
static ControlRef   gDataControl;       // a data browser control
static ControlRef   gAddHiddenControl;  // the "Add Hidden" checkbox
static WindowRef    gMainWindow;
 
static CFArrayRef   gItems;
 
static pascal OSStatus DataBrowserDataCallback(
    ControlRef              browser, 
    DataBrowserItemID       item, 
    DataBrowserPropertyID   property, 
    DataBrowserItemDataRef  itemData, 
    Boolean                 setValue
)
    // Called by the data browser control to get information about the 
    // data that it's displaying.  item is the row in the list, which in this 
    // case is the index (plus one) into the gItems CFArray that represents 
    // the login items list.  property is the column in the list; these values 
    // are set in the .nib file.  When asked for data, the routine looks up 
    // the item'th element of gItems and then extracts the appropriate key 
    // from that dictionary.
{
    OSStatus        err;
    CFDictionaryRef dict;
    CFBooleanRef    hidden;
    CFURLRef        url;
    CFStringRef     str;
    
    assert(browser == gDataControl);
    assert( (item != kDataBrowserNoItem) && (item <= CFArrayGetCount(gItems)) );
    assert( (property < 1024) || (property == 'hidn') || (property == 'URL ') );
    assert( ! setValue );
    
    // gItems can only be NULL before the first call to DoRefresh as the 
    // application starts up.  Data browser should never be calling us to 
    // get information about items during that time, because we haven't 
    // added any items to the data browser.  Thus, we shouldn't be called 
    // when gItems is NULL.
 
    assert( gItems != NULL );
    
    switch (property) {
        case 'hidn':
            dict = (CFDictionaryRef) CFArrayGetValueAtIndex(gItems, (CFIndex) (item - 1));
            assert( (dict != NULL) && (CFGetTypeID(dict) == CFDictionaryGetTypeID()) );
            
            hidden = (CFBooleanRef) CFDictionaryGetValue(dict, kLIAEHidden);
            assert( (hidden != NULL) && (CFGetTypeID(hidden) == CFBooleanGetTypeID()) );
 
            err = SetDataBrowserItemDataText(
                itemData, 
                (CFBooleanGetValue(hidden) ? CFSTR("yes") : CFSTR("no"))
            );
            break;
        case 'URL ':
            dict = (CFDictionaryRef) CFArrayGetValueAtIndex(gItems, (CFIndex) (item - 1));
            assert( (dict != NULL) && (CFGetTypeID(dict) == CFDictionaryGetTypeID()) );
 
            url = (CFURLRef) CFDictionaryGetValue(dict, kLIAEURL);
            assert( (url != NULL) && (CFGetTypeID(url) == CFURLGetTypeID()) );
 
            str = CFURLGetString(url);
            
            err = SetDataBrowserItemDataText(itemData, str);
            break;
        default:
            err = noErr;
            break;
    }
 
    assert(err == noErr);
 
    return err;
}
 
static void DoRefresh(void)
    // Called in response to a click of the "Refresh" button.  
    // Also called to refresh the list after other actions have 
    // changed it.  Also called at application startup to 
    // establish an initial value for the list.
{
    OSStatus            err;
    CFArrayRef          items;
    CFIndex             itemCount;
    CFIndex             itemIndex;
    DataBrowserItemID * itemsIDArray;
    
    itemsIDArray = NULL;
    items = NULL;
    
    // Get the array from LoginItemsAE.
    
    err = LIAECopyLoginItems(&items);
    if (err == noErr) {
    
        // Swap it into gItems.
        
        CFQRelease(gItems);
        gItems = items;
        items = NULL;       // to prevent the release at the end of this function
    
        // Remove any existing items.
            
        if (err == noErr) {
            err = RemoveDataBrowserItems(
                gDataControl,
                kDataBrowserNoItem,                 // from root
                0,                                  // all items
                NULL,                               //  "    "
                kDataBrowserItemNoProperty
            );
        }   
        
        // Add the new items.
        
        if (err == noErr) {
            itemCount = CFArrayGetCount(gItems);
        
            itemsIDArray = calloc(itemCount, sizeof(DataBrowserItemID));
            if (itemsIDArray == NULL) {
                err = memFullErr;
            }
        }
        if (err == noErr) {
        
            // Each item ID is the item's index into the gItems array, 
            // plus one because a value of 0 is invalid (it's kDataBrowserNoItem).
            
            for (itemIndex = 0; itemIndex < itemCount; itemIndex++) {
                itemsIDArray[itemIndex] = (DataBrowserItemID) (itemIndex + 1);
            }
        
            err = AddDataBrowserItems(
                gDataControl,
                kDataBrowserNoItem,                 // to root
                (UInt32) itemCount,
                itemsIDArray,
                kDataBrowserItemNoProperty
            );
        }
    }
    
    // Clean up.
    
    CFQRelease(items);
    free(itemsIDArray);
 
    DisplayError(err);
}
 
static NavEventUPP gAddNavEventUPP;         // AddNavEvent
 
static pascal void AddNavEvent(
    NavEventCallbackMessage callBackSelector, 
    NavCBRecPtr             callBackParms, 
    void *                  callBackUD
)
    // Called by Navigation Services when interesting things happen 
    // in our Nav dialog (which is displayed when the user clicks 
    // the "Add" button).  In this case we're primarily interested 
    // in two events: the user action of the user clicking the Choose 
    // button of the Nav dialog, and the dialog being torn down.
{
    #pragma unused(callBackUD)
    OSStatus        err;
    OSStatus        junk;
    NavDialogRef    navDialog;
    
    navDialog = callBackParms->context;
    assert(navDialog != NULL);
    
    switch (callBackSelector) {
        case kNavCBUserAction:
            switch ( NavDialogGetUserAction(navDialog) ) {
                case kNavUserActionChoose:
                    {
                        NavReplyRecord  reply;
                        AEKeyword       junkKeyword;
                        DescType        junkType;
                        Size            junkSize;
                        
                        err = NavDialogGetReply(navDialog, &reply);
                        if (err == noErr) {
                            FSRef   chosenItem;
                            
                            // In the debug build, verify that only one items is 
                            // selected.
                            
                            #if ! defined(NDEBUG)
                                {
                                    long selectionCount;
                                    
                                    assert( 
                                           (AECountItems( &reply.selection, &selectionCount) == noErr)
                                        && (selectionCount == 1)
                                    );
                                }
                            #endif
 
                            // Get the selected item.
                            
                            err = AEGetNthPtr(
                                &reply.selection, 
                                1, 
                                typeFSRef, 
                                &junkKeyword, 
                                &junkType, 
                                &chosenItem, 
                                sizeof(chosenItem), 
                                &junkSize
                            );
                            
                            // Use LoginItemsAE to add it to the list.
                            
                            if (err == noErr) {
                                err = LIAEAddRefAtEnd(
                                    &chosenItem, 
                                    GetControlValue(gAddHiddenControl) != 0
                                );
                            }
                        
                            junk = NavDisposeReply(&reply);
                            assert(junk == noErr);
                            
                            if (err == noErr) {
                                DoRefresh();
                            } else {
                                DisplayError(err);
                            }                       
                        }
                    }
                    break;
                default:
                    // do nothing
                    break;
            }
            break;
        case kNavCBTerminate:
            NavDialogDispose(navDialog);
            break;
        default:
            // do nothing
            break;
    }
}
 
static void DoAddTest(void)
    // Called in response to a click of the "Add" button. 
{
    OSStatus                    err;
    NavDialogCreationOptions    navOptions;
    NavDialogRef                navDialog;
 
    navDialog = NULL;
    
    if (gAddNavEventUPP == NULL) {
        gAddNavEventUPP = NewNavEventUPP(AddNavEvent);
        assert(gAddNavEventUPP != NULL);
    }
 
    // Create and run a Nav choose object dialog (which lets you 
    // choose a file, folder or volume).
    
    err = NavGetDefaultDialogCreationOptions(&navOptions);
    if (err == noErr) {
        navOptions.optionFlags  |= kNavSupportPackages;
        navOptions.modality      = kWindowModalityWindowModal;
        navOptions.parentWindow  = gMainWindow;
        
        err = NavCreateChooseObjectDialog(
            &navOptions,                    // inOptions
            gAddNavEventUPP,
            NULL,                           // inPreviewProc
            NULL,                           // inFilterProc
            NULL,                           // inClientData
            &navDialog
        );
    }
    if (err == noErr) {
        err = NavDialogRun(navDialog);
        
        // The process of adding the item continues in AddNavEvent 
        // when the user has chosen an item to add.
    }
 
    // Clean up.
 
    // Dispose the Nav dialog if we didn't manage to start it running.
    
    if (err != noErr) {
        if (navDialog != NULL) {
            NavDialogDispose(navDialog);
        }
    }
 
    DisplayError(err);
}
 
static void DoRemoveTest(void)
    // Called in response to a click of the "Remove" button. 
{
    OSStatus    err;
    CFIndex     itemCount;
    CFIndex     itemIndex;
    Boolean     found;
    
    // Find the index of the selected item in the data browser 
    // control.
    
    itemCount = CFArrayGetCount(gItems);
    itemIndex = 0;
    found = false;
    while ( (itemIndex < itemCount) && ! found ) {
        found = IsDataBrowserItemSelected(gDataControl, (DataBrowserItemID) itemIndex + 1);;
        
        if ( ! found ) {
            itemIndex += 1;
        }
    }
    
    // Use LIAE to remove it.
    
    if (found) {
        err = LIAERemove(itemIndex);
    } else {
        // It's not an error to have no selection because we don't dynamically 
        // enable/disable the Remove button.
        err = noErr;
    }
    
    if (err == noErr) {
        DoRefresh();
    } else {
        DisplayError(err);
    }   
}
 
static EventHandlerUPP gApplicationEventHandlerUPP;     // -> ApplicationEventHandler
 
static const EventTypeSpec kApplicationEvents[] = { {kEventClassCommand, kEventCommandProcess} };
 
static pascal OSStatus ApplicationEventHandler(EventHandlerCallRef inHandlerCallRef, 
                                               EventRef inEvent, void *inUserData)
    // Dispatches HICommands to their implementations.
{
    OSStatus    err;
    HICommand   command;
    #pragma unused(inHandlerCallRef)
    #pragma unused(inUserData)
    
    assert( GetEventClass(inEvent) == kEventClassCommand  );
    assert( GetEventKind(inEvent)  == kEventCommandProcess);
    
    err = GetEventParameter(inEvent, kEventParamDirectObject, typeHICommand, NULL, sizeof(command), NULL, &command);
    if (err == noErr) {
        switch (command.commandID) {
            case kHICommandAbout:
                DoAbout();
                break;
            case 'DShw':
                DoRefresh();
                break;
            case 'DAdd':
                DoAddTest();
                break;
            case 'DRem':
                DoRemoveTest();
                break;
            default:
                err = eventNotHandledErr;
                break;
        }
    }
    
    return err;
}
 
static OSStatus SetupUserInterface(void)
    // Create a user interface from our NIB.
{
    OSStatus    err;
    IBNibRef    nibRef;
    Handle      menuBar;
 
    nibRef  = NULL;
    menuBar = NULL;
        
    err = CreateNibReference(CFSTR("LoginItemsAETest"), &nibRef);
    if (err == noErr) {
        err = CreateMenuBarFromNib(nibRef, CFSTR("MenuBar"), &menuBar);
    }
    if (err == noErr) {
        SetMenuBar(menuBar);
    }
    if (err == noErr) {
        err = CreateWindowFromNib(nibRef, CFSTR("MainWindow"), &gMainWindow);
    }
    if (err == noErr) {
        ControlID theID;
 
        theID.signature = 'HDCB';
        theID.id = 0;   
 
        err = GetControlByID(gMainWindow, &theID, &gAddHiddenControl);
    }
 
    // Find and set up the data browser control.
    
    if (err == noErr) {
        ControlID theID;
 
        theID.signature = 'DATA';
        theID.id = 0;   
 
        err = GetControlByID(gMainWindow, &theID, &gDataControl);
    }
    if (err == noErr) {
        DataBrowserCallbacks callbacks;
        
        callbacks.version = kDataBrowserLatestCallbacks;
        err = InitDataBrowserCallbacks(&callbacks);
 
        if (err == noErr) {
            callbacks.u.v1.itemDataCallback = NewDataBrowserItemDataUPP(DataBrowserDataCallback);
 
            err = SetDataBrowserCallbacks(gDataControl, &callbacks);
        }
    }
    if (err == noErr) {
        DoRefresh();
    }
    
    if (err == noErr) {
        ShowWindow(gMainWindow);
    }
    
    if (nibRef != NULL) {
        DisposeNibReference(nibRef);
    }
    if (menuBar != NULL) {
        DisposeHandle(menuBar);
    }
    return err;
}
 
int main(void)
{
    OSStatus    err;
    UInt32      response;
        
    err = Gestalt(gestaltSystemVersion, (long *) &response);
    if (err == noErr) {
        if ( response < 0x1020 ) {
            SInt16 junkHit;
            
            (void) StandardAlert(
                kAlertStopAlert, 
                "\pLoginItemsAETest requires Mac OS X 10.2 or later.", 
                "\p",
                NULL, 
                &junkHit
            );
            
            err = userCanceledErr;
        }
    }
    
    // Start up the UI.
    
    if (err == noErr) {
        err = SetupUserInterface();
    }
    
    // Install our HICommand handler.
    
    if (err == noErr) {
        gApplicationEventHandlerUPP = NewEventHandlerUPP(ApplicationEventHandler);
        assert(gApplicationEventHandlerUPP != NULL);
 
        err = InstallApplicationEventHandler(gApplicationEventHandlerUPP, 
                                             GetEventTypeCount(kApplicationEvents), 
                                             kApplicationEvents, NULL, NULL);
    }
    
    // Run the application.
    
    if (err == noErr) {
        RunApplicationEventLoop();
    }
 
    DisplayError(err);
 
    return 0;
}