Saving and opening files

Previously, we saw how we could build a framework GUI to translate the configuration options for a given ALife model into a configuration window that could provide an NSDictionary of key-value pairs to give to our ALife models. That was pretty keen.

However, now that we can generate configurations, we want to be able to save those configuration files, and, if possible, execute them headlessly from the command-line, for grid computation purposes. The use case is, we define some parameters (some of them randomly set within a range), and send that configuration out to the grid. Then we can examine the results later, and pick out those that display interesting behavior for further study.

We'll implement the GUI and backend support for randomized input value later. Here, we'll look at

  1. the process for generating PList configuration files from our configuration,
  2. being able to open those configuration files from the GUI, and
  3. creating a command-line executable to run a model for a certain number of generations headlessly.

The first is pretty dead-easy. Recall that we've got an ALifeConfigurationViewController, which loads the configuration options for a specified ALifeController class and displays the options (OptionViewControllers, to be precise) in a GUI. Each of the OptionViewControllers knows its current value, and the key with which it's associated; this allows us to build a dictionary of key:value pairs which we can feed to the ALifeController to initialize a configuration.

We've seen before how easy it is to build dictionaries from a plist file; it turns out it's just as easy to go the other way. We'll add a button to our configuration window, next to the Start button to export the current configuration, with a corresponding IBAction:

- (IBAction)actionExportConfiguration:(id)sender;

All this action does is show a save panel, specifying a file extension which our application will support: - (IBAction)actionExportConfiguration:(id)sender; { NSSavePanel *savePanel = [NSSavePanel savePanel]; [savePanel setRequiredFileType:@"cocoabugs"]; [savePanel beginSheetForDirectory:nil file:nil modalForWindow:[self window] modalDelegate:self didEndSelector:@selector(savePanelDidEnd:returnCode:contextInfo:) contextInfo:nil]; } This method shows a save panel in a window-modal sheet, and our ALifeConfigurationWindowController will get sent the savePanelDidEnd:... selector when that panel is closed by the user (by pressing either "Save" or "Cancel").

In this method, we want to: 1. Check if the Save button was clicked; 2. Generate a dictionary specifying the configuration and a class identifier; 3. Write the dictionary to a file. The code is fairly self-explanatory, utilizing NSDictionary's -writeToFile: method to save the dictioniary in plist format:

- (void)savePanelDidEnd:(NSSavePanel *)sheet returnCode:(int)returnCode  contextInfo:(void  *)contextInfo;
{
    if (returnCode == NSOKButton) {
        NSDictionary *configuration = [configurationViewController configuration];
        NSDictionary *data = [NSDictionary dictionaryWithObjectsAndKeys:
                                [[self selectedClass] name], @"identifier",
                                configuration, @"configuration", nil];
        [data writeToFile:[sheet filename] atomically:YES];
    }
}

Now we need to do a little work to allow these files to be double-clickable. In XCode, the Get Info window for the CocoaBugs target has a "Properties" tab which lets us edit the Info.plist values directly. Here we can add a new document type for our app to support:

cocoabugs document types.png

Now when a file with extension .cocoabugs is double-clicked, our app will be sent the message

- (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename

This method will be very similar to the actionStartSimulation method in our ALifeConfigurationWindowController.

- (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename
{
    NSDictionary *data = [NSDictionary dictionaryWithContentsOfFile:filename];

    NSString *identifier = [data objectForKey:@"identifier"];
    NSDictionary *configuration = [data objectForKey:@"configuration"];

    Class selectedPlugin;
    NSArray *plugins = [self allPlugIns];
    for (Class <ALifeController> plugin in plugins) {
        if ([[plugin name] isEqual:identifier]) {
            selectedPlugin = plugin;
            break;
        }
    }

    ALifeWindowController *simulationWindow =
        [ALifeWindowController windowControllerForModel:selectedPlugin
                                      withConfiguration:configuration];

    return YES;
}

We loop through the loaded plugins, looking for the plugin matching the identifier set in the plist; once found, we open a new simulation window with an ALifeController initialized with the specified configuration.

Now we can set some options in the configuration controller, export to a file, and double-click it to run the simulation.

Nice!

So, that's parts 1 and 2 under our belts. Part 3 will take a little more doing, and a little more refactoring. You'll just have to wait and see how that pans out... because I haven't done it yet. OH THE HUMANITY

Leave a comment