project_files/HedgewarsMobile/Classes/MapConfigViewController.m
changeset 3547 02875b1145b7
parent 3546 ccf4854df294
child 3598 a8aa06bae895
--- a/project_files/HedgewarsMobile/Classes/MapConfigViewController.m	Wed Jun 23 21:49:19 2010 +0200
+++ b/project_files/HedgewarsMobile/Classes/MapConfigViewController.m	Wed Jun 23 22:03:56 2010 +0200
@@ -0,0 +1,568 @@
+//
+//  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);
+    previewCGImage = nil;
+
+    // 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 makeRoundCornersOfSize:CGSizeMake(12, 12)] waitUntilDone:NO];
+    [previewImage release];
+    [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];
+    [image release];
+    
+    // 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 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