cocoaTouch/MapConfigViewController.m
author koda
Thu, 17 Jun 2010 19:57:51 +0200
changeset 3513 f589230fa21b
parent 3495 a6b4f351d400
permissions -rw-r--r--
now it's possible to select the scheme file in the ifrontendfix a type about loading an image (iphone file system IS case senstive) add rotation for iphone build too make the ifrontend work again with 3.0 sdk reworked openalbridge following most of an old implementation by Smaxx and making it more modular -- now sources are limited but the memory extension and cleanup is todo nil'd many variables in engine that were causing intialization problems

//
//  MapConfigViewController.m
//  HedgewarsMobile
//
//  Created by Vittorio on 22/04/10.
//  Copyright 2010 __MyCompanyName__. All rights reserved.
//

#import "MapConfigViewController.h"
#import "PascalImports.h"
#import "CommodityFunctions.h"
#import "UIImageExtra.h"
#import "SDL_net.h"
#import <pthread.h>

#define INDICATOR_TAG 7654

@implementation MapConfigViewController
@synthesize previewButton, maxHogs, seedCommand, templateFilterCommand, mapGenCommand, mazeSizeCommand, themeCommand,
            tableView, maxLabel, sizeLabel, segmentedControl, slider, lastIndexPath, themeArray, mapArray, busy;


-(BOOL) shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
    return rotationManager(interfaceOrientation);
}

#pragma mark -
#pragma mark Preview Handling
-(int) sendToEngine: (NSString *)string {
	unsigned char length = [string length];
	
	SDLNet_TCP_Send(csd, &length , 1);
	return SDLNet_TCP_Send(csd, [string UTF8String], length);
}

-(const uint8_t *)engineProtocol:(NSInteger) port {
	IPaddress ip;
	BOOL serverQuit = NO;
    static uint8_t map[128*32];
    
	if (SDLNet_Init() < 0) {
		NSLog(@"SDLNet_Init: %s", SDLNet_GetError());
        serverQuit = YES;
	}
	
	// Resolving the host using NULL make network interface to listen
	if (SDLNet_ResolveHost(&ip, NULL, port) < 0) {
		NSLog(@"SDLNet_ResolveHost: %s\n", SDLNet_GetError());
        serverQuit = YES;
	}
	
	// Open a connection with the IP provided (listen on the host's port)
	if (!(sd = SDLNet_TCP_Open(&ip))) {
		NSLog(@"SDLNet_TCP_Open: %s %\n", SDLNet_GetError(), port);
        serverQuit = YES;
	}
	
    // launch the preview here so that we're sure the tcp channel is open
    pthread_t thread_id;
    pthread_create(&thread_id, NULL, (void *)GenLandPreview, (void *)port);
    pthread_detach(thread_id);
    
	DLog(@"Waiting for a client on port %d", port);
	while (!serverQuit) {
		/* This check the sd if there is a pending connection.
		 * If there is one, accept that, and open a new socket for communicating */
		csd = SDLNet_TCP_Accept(sd);
		if (NULL != csd) {			
			DLog(@"Client found");
            
            [self sendToEngine:self.seedCommand];
            [self sendToEngine:self.templateFilterCommand];
            [self sendToEngine:self.mapGenCommand];
            [self sendToEngine:self.mazeSizeCommand];
            [self sendToEngine:@"!"];
                
            memset(map, 0, 128*32);
            SDLNet_TCP_Recv(csd, map, 128*32);
            SDLNet_TCP_Recv(csd, &maxHogs, sizeof(uint8_t));

			SDLNet_TCP_Close(csd);
			serverQuit = YES;
		}
	}
	
	SDLNet_TCP_Close(sd);
	SDLNet_Quit();
    return map;
}

-(void) drawingThread {
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    
    // select the port for IPC and launch the preview generation through engineProtocol:
    int port = randomPort();
    const uint8_t *map = [self engineProtocol:port];
    uint8_t mapExp[128*32*8];

    // draw the buffer (1 pixel per component, 0= transparent 1= color)
    int k = 0;
    for (int i = 0; i < 32*128; i++) {
        unsigned char byte = map[i];
        for (int j = 0; j < 8; j++) {
            // select the color based on the leftmost bit
            if ((byte & 0x80) != 0)
                mapExp[k] = 100;
            else
                mapExp[k] = 255;
            // shift to next bit
            byte <<= 1;
            k++;
        }
    }
    CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceGray();
    CGContextRef bitmapImage = CGBitmapContextCreate(mapExp, 256, 128, 8, 256, colorspace, kCGImageAlphaNone);
    CGColorSpaceRelease(colorspace);
    
    CGImageRef previewCGImage = CGBitmapContextCreateImage(bitmapImage);
    UIImage *previewImage = [[UIImage alloc] initWithCGImage:previewCGImage];
    CGImageRelease(previewCGImage);

    // set the preview image (autoreleased) in the button and the maxhog label on the main thread to prevent a leak
    [self performSelectorOnMainThread:@selector(setButtonImage:) withObject:[[previewImage retain] makeRoundCornersOfSize:CGSizeMake(12, 12)] waitUntilDone:NO];
    [self performSelectorOnMainThread:@selector(setLabelText:) withObject:[NSString stringWithFormat:@"%d", maxHogs] waitUntilDone:NO];
    
    // restore functionality of button and remove the spinning wheel on the main thread to prevent a leak
    [self performSelectorOnMainThread:@selector(turnOnWidgets) withObject:nil waitUntilDone:NO];
    
    [pool release];
    //Invoking this method should be avoided as it does not give your thread a chance to clean up any resources it allocated during its execution.
    //[NSThread exit];

    /*
    // http://developer.apple.com/mac/library/qa/qa2001/qa1037.html
    UIGraphicsBeginImageContext(CGSizeMake(256,128));      
    CGContextRef context = UIGraphicsGetCurrentContext();       
    UIGraphicsPushContext(context);  

    CGContextSetRGBFillColor(context, 0.5, 0.5, 0.7, 1.0);
    CGContextFillRect(context,CGRectMake(xc,yc,1,1));
    
    UIGraphicsPopContext();
    UIImage *previewImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    */
}

-(IBAction) updatePreview {
    // don't generate a new preview while it's already generating one
    if (busy)
        return;
    
    // generate a seed
    CFUUIDRef uuid = CFUUIDCreate(kCFAllocatorDefault);
    NSString *seed = (NSString *)CFUUIDCreateString(kCFAllocatorDefault, uuid);
    CFRelease(uuid);
    NSString *seedCmd = [[NSString alloc] initWithFormat:@"eseed {%@}", seed];
    [seed release];
    self.seedCommand = seedCmd;
    [seedCmd release];
    
    NSIndexPath *theIndex;
    if (segmentedControl.selectedSegmentIndex != 1) {
        // prevent other events and add an activity while the preview is beign generated
        [self turnOffWidgets];
        
        // remove the current preview
        [self.previewButton setImage:nil forState:UIControlStateNormal];
        
        // add a very nice spinning wheel
        UIActivityIndicatorView *indicator = [[UIActivityIndicatorView alloc] 
                                              initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
        indicator.center = CGPointMake(previewButton.bounds.size.width / 2, previewButton.bounds.size.height / 2);
        indicator.tag = INDICATOR_TAG;
        [indicator startAnimating];
        [self.previewButton addSubview:indicator];
        [indicator release];
        
        // let's draw in a separate thread so the gui can work; at the end it restore other widgets
        [NSThread detachNewThreadSelector:@selector(drawingThread) toTarget:self withObject:nil];
    
        theIndex = [NSIndexPath indexPathForRow:(random()%[self.themeArray count]) inSection:0];
    } else {
        theIndex = [NSIndexPath indexPathForRow:(random()%[self.mapArray count]) inSection:0];
    }
    [self.tableView reloadData];
    [self tableView:self.tableView didSelectRowAtIndexPath:theIndex];
    [self.tableView scrollToRowAtIndexPath:theIndex atScrollPosition:UITableViewScrollPositionNone animated:YES];
}

-(void) updatePreviewWithMap:(NSInteger) index {
    // change the preview button
    NSString *fileImage = [[NSString alloc] initWithFormat:@"%@/%@/preview.png", MAPS_DIRECTORY(),[self.mapArray objectAtIndex:index]];
    UIImage *image = [[UIImage alloc] initWithContentsOfFile:fileImage];
    [fileImage release];
    [self.previewButton setImage:[image makeRoundCornersOfSize:CGSizeMake(12, 12)] forState:UIControlStateNormal];

    // update label
    maxHogs = 18;
    NSString *fileCfg = [[NSString alloc] initWithFormat:@"%@/%@/map.cfg", MAPS_DIRECTORY(),[self.mapArray objectAtIndex:index]];
    NSString *contents = [[NSString alloc] initWithContentsOfFile:fileCfg encoding:NSUTF8StringEncoding error:NULL];
    [fileCfg release];
    NSArray *split = [contents componentsSeparatedByString:@"\n"];

    // if the number is not set we keep 18 standard; 
    // sometimes it's not set but there are trailing characters, we get around them with the second equation
    if ([split count] > 1 && [[split objectAtIndex:1] intValue] > 0)
        maxHogs = [[split objectAtIndex:1] intValue];
    [contents release];
    NSString *max = [[NSString alloc] initWithFormat:@"%d",maxHogs];
    self.maxLabel.text = max;
    [max release];
}

-(void) turnOffWidgets {
    busy = YES;
    self.previewButton.alpha = 0.5f;
    self.previewButton.enabled = NO;
    self.maxLabel.text = @"...";
    self.segmentedControl.enabled = NO;
    self.slider.enabled = NO;
}

-(void) turnOnWidgets {
    self.previewButton.alpha = 1.0f;
    self.previewButton.enabled = YES;
    self.segmentedControl.enabled = YES;
    self.slider.enabled = YES;
    busy = NO;
    
    UIActivityIndicatorView *indicator = (UIActivityIndicatorView *)[self.previewButton viewWithTag:INDICATOR_TAG];
    if (indicator) {
        [indicator stopAnimating];
        [indicator removeFromSuperview];
    }
}
 
-(void) setLabelText:(NSString *)str {
    self.maxLabel.text = str;
}

-(void) setButtonImage:(UIImage *)img {
    [self.previewButton setBackgroundImage:img forState:UIControlStateNormal];
}

-(void) restoreBackgroundImage {
    // white rounded rectangle as background image for previewButton
    UIGraphicsBeginImageContext(CGSizeMake(256,128));      
    CGContextRef context = UIGraphicsGetCurrentContext();       
    UIGraphicsPushContext(context);  

    CGContextSetRGBFillColor(context, 1.0, 1.0, 1.0, 1.0);
    CGContextFillRect(context,CGRectMake(0,0,256,128));
    
    UIGraphicsPopContext();
    UIImage *bkgImg = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    [self.previewButton setBackgroundImage:[[bkgImg retain] makeRoundCornersOfSize:CGSizeMake(12, 12)] forState:UIControlStateNormal];
}

#pragma mark -
#pragma mark Table view data source
-(NSInteger) numberOfSectionsInTableView:(UITableView *)tableView {
    return 1;
}

-(NSInteger) tableView:(UITableView *)aTableView numberOfRowsInSection:(NSInteger) section {
    if (self.segmentedControl.selectedSegmentIndex != 1)
        return [themeArray count];
    else
        return [mapArray count];
}

-(UITableViewCell *)tableView:(UITableView *)aTableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *CellIdentifier = @"Cell";
    NSInteger row = [indexPath row];
    
    UITableViewCell *cell = [aTableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) 
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
    
    if (self.segmentedControl.selectedSegmentIndex != 1) {
        // the % prevents a strange bug that occurs sporadically
        NSString *themeName = [self.themeArray objectAtIndex:row % [self.themeArray count]];
        cell.textLabel.text = themeName;
        UIImage *image = [[UIImage alloc] initWithContentsOfFile:[NSString stringWithFormat:@"%@/%@/icon.png",THEMES_DIRECTORY(),themeName]];
        cell.imageView.image = image;
        [image release];
    } else {
        cell.textLabel.text = [self.mapArray objectAtIndex:row];
        cell.imageView.image = nil;
    }
    
    if (row == [self.lastIndexPath row]) 
        cell.accessoryType = UITableViewCellAccessoryCheckmark;
    else
        cell.accessoryType = UITableViewCellAccessoryNone;

    return cell;
}


#pragma mark -
#pragma mark Table view delegate
-(void) tableView:(UITableView *)aTableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    int newRow = [indexPath row];
    int oldRow = (lastIndexPath != nil) ? [lastIndexPath row] : -1;
    
    if (newRow != oldRow) {
        if (self.segmentedControl.selectedSegmentIndex != 1) {
            NSString *theme = [self.themeArray objectAtIndex:newRow];
            self.themeCommand =  [NSString stringWithFormat:@"etheme %@", theme];
        } else
            [self updatePreviewWithMap:newRow];

        UITableViewCell *newCell = [aTableView cellForRowAtIndexPath:indexPath]; 
        newCell.accessoryType = UITableViewCellAccessoryCheckmark;
        UITableViewCell *oldCell = [aTableView cellForRowAtIndexPath:self.lastIndexPath];
        oldCell.accessoryType = UITableViewCellAccessoryNone;

        self.lastIndexPath = indexPath;
        [aTableView selectRowAtIndexPath:indexPath animated:YES scrollPosition:UITableViewScrollPositionNone];
    }
    [aTableView deselectRowAtIndexPath:indexPath animated:YES];
}

#pragma mark -
#pragma mark slider & segmentedControl
// this updates the label and the command keys when the slider is moved, depending of the selection in segmentedControl
// no methods are called by this routine and you can pass nil to it
-(IBAction) sliderChanged:(id) sender {
    NSString *labelText;
    NSString *templateCommand;
    NSString *mazeCommand;
    
    switch ((int)(self.slider.value*100)) {
        case 0:
            if (self.segmentedControl.selectedSegmentIndex == 0) {
                labelText = NSLocalizedString(@"Wacky",@"");
            } else {
                labelText = NSLocalizedString(@"Large Floating Islands",@"");
            }
            templateCommand = @"e$template_filter 5";
            mazeCommand = @"e$maze_size 5";
            break;
        case 1:
            if (self.segmentedControl.selectedSegmentIndex == 0) {
                labelText = NSLocalizedString(@"Cavern",@"");
            } else {
                labelText = NSLocalizedString(@"Medium Floating Islands",@"");
            }
            templateCommand = @"e$template_filter 4";
            mazeCommand = @"e$maze_size 4";
            break;
        case 2:
            if (self.segmentedControl.selectedSegmentIndex == 0) {
                labelText = NSLocalizedString(@"Small",@"");
            } else {
                labelText = NSLocalizedString(@"Small Floating Islands",@"");
            }
            templateCommand = @"e$template_filter 1";
            mazeCommand = @"e$maze_size 3";
            break;
        case 3:
            if (self.segmentedControl.selectedSegmentIndex == 0) {
                labelText = NSLocalizedString(@"Medium",@"");
            } else {
                labelText = NSLocalizedString(@"Large Tunnels",@"");
            }
            templateCommand = @"e$template_filter 2";
            mazeCommand = @"e$maze_size 2";
            break;
        case 4:
            if (self.segmentedControl.selectedSegmentIndex == 0) {
                labelText = NSLocalizedString(@"Large",@"");
            } else {
                labelText = NSLocalizedString(@"Medium Tunnels",@"");
            }
            templateCommand = @"e$template_filter 3";
            mazeCommand = @"e$maze_size 1";
            break;
        case 5:
            if (self.segmentedControl.selectedSegmentIndex == 0) {
                labelText = NSLocalizedString(@"All",@"");
            } else {
                labelText = NSLocalizedString(@"Small Tunnels",@"");
            }
            templateCommand = @"e$template_filter 0";
            mazeCommand = @"e$maze_size 0";
            break;
        default:
            labelText = nil;
            templateCommand = nil;
            mazeCommand = nil;
            break;
    }
    
    self.sizeLabel.text = labelText;
    self.templateFilterCommand = templateCommand;
    self.mazeSizeCommand = mazeCommand;
}

// update preview (if not busy and if its value really changed) as soon as the user lifts its finger up
-(IBAction) sliderEndedChanging:(id) sender {
    int num = (int) (self.slider.value * 100);
    if (oldValue != num) {
        [self updatePreview];
        oldValue = num;
    }
}

// perform actions based on the activated section, then call updatePreview to visually update the selection 
// updatePreview will call didSelectRowAtIndexPath which will call the right update routine)
// and if necessary update the table with a slide animation
-(IBAction) segmentedControlChanged:(id) sender {
    NSString *mapgen;
    NSInteger newPage = self.segmentedControl.selectedSegmentIndex;
    
    switch (newPage) {
        case 0: // Random
            mapgen = @"e$mapgen 0";
            [self sliderChanged:nil];
            self.slider.enabled = YES;
            break;
            
        case 1: // Map
            mapgen = @"e$mapgen 0";
            self.slider.enabled = NO;
            self.sizeLabel.text = @".";
            [self restoreBackgroundImage];
            break;
            
        case 2: // Maze
            mapgen = @"e$mapgen 1";
            [self sliderChanged:nil];
            self.slider.enabled = YES;
            break;
        
        default:
            mapgen = nil;
            break;
    }
    self.mapGenCommand = mapgen;
    [self updatePreview];
    
    // nice animation for updating the table when appropriate (on iphone)
    if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone)
        if (((oldPage == 0 || oldPage == 2) && newPage == 1) ||
            (oldPage == 1 && (newPage == 0 || newPage == 2))) {
            [UIView beginAnimations:@"moving out table" context:NULL];
            self.tableView.frame = CGRectMake(480, 0, 185, 276);
            [UIView commitAnimations];
            [self performSelector:@selector(moveTable) withObject:nil afterDelay:0.2];
        }
    oldPage = newPage;
}

// update data when table is not visible and then show it
-(void) moveTable {
    [self.tableView reloadData];
    
    [UIView beginAnimations:@"moving in table" context:NULL];
    self.tableView.frame = CGRectMake(295, 0, 185, 276);
    [UIView commitAnimations];
}

#pragma mark -
#pragma mark view management
-(void) viewDidLoad {
    [super viewDidLoad];
    
    srandom(time(NULL));
    
    CGSize screenSize = [[UIScreen mainScreen] bounds].size;
    self.view.frame = CGRectMake(0, 0, screenSize.height, screenSize.width - 44);

    // themes.cfg contains all the user-selectable themes
    NSString *string = [[NSString alloc] initWithContentsOfFile:[THEMES_DIRECTORY() stringByAppendingString:@"/themes.cfg"]
                                                       encoding:NSUTF8StringEncoding 
                                                          error:NULL];
    NSMutableArray *array = [[NSMutableArray alloc] initWithArray:[string componentsSeparatedByString:@"\n"]];
    [string release];
    // remove a trailing "" element
    [array removeLastObject];
    self.themeArray = array;
    [array release];
    self.mapArray = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:MAPS_DIRECTORY() error:NULL];

    self.tableView.rowHeight = 42;
    busy = NO;
    
    // draw a white background
    [self restoreBackgroundImage];
    
    // initialize some "default" values
    self.sizeLabel.text = NSLocalizedString(@"All",@"");
    self.slider.value = 0.05f;
    self.segmentedControl.selectedSegmentIndex = 0;

    self.templateFilterCommand = @"e$template_filter 0";
    self.mazeSizeCommand = @"e$maze_size 0";
    self.mapGenCommand = @"e$mapgen 0";
    self.lastIndexPath = [NSIndexPath indexPathForRow:0 inSection:0];
    
    oldValue = 5;
    oldPage = 0;
}

-(void) viewDidAppear:(BOOL) animated {
    [super viewDidAppear:animated];
    [self updatePreview];
}

#pragma mark -
#pragma mark memory
-(void) didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
}

-(void) viewDidUnload {
    self.previewButton = nil;
    self.seedCommand = nil;
    self.templateFilterCommand = nil;
    self.mapGenCommand = nil;
    self.mazeSizeCommand = nil;
    self.themeCommand = nil;
    
    self.previewButton = nil;
    self.tableView = nil;
    self.maxLabel = nil;
    self.sizeLabel = nil;
    self.segmentedControl = nil;
    self.slider = nil;
    
    self.lastIndexPath = nil;
    self.themeArray = nil;
    self.mapArray = nil;
    
    [super viewDidUnload];
    MSG_DIDUNLOAD();
}

-(void) dealloc {
    [seedCommand release];
    [templateFilterCommand release];
    [mapGenCommand release];
    [mazeSizeCommand release];
    [themeCommand release];
    
    [previewButton release];
    [tableView release];
    [maxLabel release];
    [sizeLabel release];
    [segmentedControl release];
    [slider release];
    
    [lastIndexPath release];
    [themeArray release];
    [mapArray release];
    
    [super dealloc];
}


@end