diff -r f589230fa21b -r 59dbd31e9953 project_files/HedgewarsMobile/Classes/MapConfigViewController.m --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/project_files/HedgewarsMobile/Classes/MapConfigViewController.m Thu Jun 17 20:30:39 2010 +0200 @@ -0,0 +1,565 @@ +// +// 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 + +#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