1// 2// MapGridView.m 3// avida/apps/viewer-macos 4// 5// Created by David on 11/23/10. 6// Copyright 2010-2011 Michigan State University. All rights reserved. 7// http://avida.devosoft.org/viewer-macos 8// 9// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the 10// following conditions are met: 11// 12// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the 13// following disclaimer. 14// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the 15// following disclaimer in the documentation and/or other materials provided with the distribution. 16// 3. Neither the name of Michigan State University, nor the names of contributors may be used to endorse or promote 17// products derived from this software without specific prior written permission. 18// 19// THIS SOFTWARE IS PROVIDED BY MICHIGAN STATE UNIVERSITY AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 20// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21// DISCLAIMED. IN NO EVENT SHALL MICHIGAN STATE UNIVERSITY OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 23// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 24// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE 25// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26// 27// Authors: David M. Bryson <david@programerror.com> 28// 29 30#import "MapGridView.h" 31 32#include "avida/viewer-core/Map.h" 33 34#include <cassert> 35#include <iostream> 36 37 38static inline CGFloat sigmoid(CGFloat x, CGFloat midpoint, CGFloat steepness) 39{ 40 CGFloat val = steepness * (x - midpoint); 41 return exp(val) / (1.0 + exp(val)); 42} 43 44 45@interface MapGridView (hidden) { 46} 47- (void) setup; 48@end 49 50@implementation MapGridView (hidden) 51- (void) setup { 52 map_width = 0; 53 map_height = 0; 54 num_colors = 0; 55 color_cache = [NSMutableArray arrayWithCapacity:255]; 56 zoom = -1; 57 selected_x = -1; 58 selected_y = -1; 59 [self setWantsLayer:YES]; 60} 61@end 62 63 64 65@implementation MapGridView 66 67- (id) initWithFrame:(NSRect)frame { 68 self = [super initWithFrame:frame]; 69 if (self) { 70 [self setup]; 71 } 72 return self; 73} 74 75 76- (void) awakeFromNib { 77 [self setup]; 78} 79 80 81- (void) drawRect:(NSRect)dirtyRect { 82 [[NSColor darkGrayColor] set]; 83 [NSBezierPath fillRect:dirtyRect]; 84 85 if (num_colors != [color_cache count]) { 86 [color_cache removeAllObjects]; 87 if (num_colors == 10) { 88 [color_cache insertObject:[NSColor greenColor] atIndex:0]; 89 [color_cache insertObject:[NSColor redColor] atIndex:1]; 90 [color_cache insertObject:[NSColor blueColor] atIndex:2]; 91 [color_cache insertObject:[NSColor cyanColor] atIndex:3]; 92 [color_cache insertObject:[NSColor yellowColor] atIndex:4]; 93 [color_cache insertObject:[NSColor magentaColor] atIndex:5]; 94 [color_cache insertObject:[NSColor orangeColor] atIndex:6]; 95 [color_cache insertObject:[NSColor purpleColor] atIndex:7]; 96 [color_cache insertObject:[NSColor brownColor] atIndex:8]; 97 [color_cache insertObject:[NSColor lightGrayColor] atIndex:9]; 98 } else { 99 for (int i = 0; i < num_colors; i++) { 100 CGFloat x = 0.1 + 0.8 * (static_cast<CGFloat>(i) / (num_colors - 1)); 101 CGFloat h = fmod((x + .27), 1.0); 102 CGFloat s = sigmoid(1.0 - x, 0.1, 30); 103 CGFloat b = sigmoid(x, 0.3, 10); 104 [color_cache insertObject:[NSColor colorWithCalibratedHue:h saturation:s brightness:b alpha:1.0] atIndex:i]; 105 } 106 } 107 } 108 109 CGFloat block_size = zoom; 110 CGFloat grid_width = (block_size > 5.0) ? 1.0 : 0.0; 111 112 // Determine Map Dimensions 113 NSRect mapRect; 114 mapRect.size.width = map_width * block_size - grid_width; 115 mapRect.size.height = map_height * block_size - grid_width; 116 mapRect.origin = NSMakePoint(0, 0); 117 118 [[NSColor blackColor] set]; 119 [NSBezierPath fillRect:mapRect]; 120 121 122 NSRect gridCellRect; 123 gridCellRect.size.width = block_size - grid_width; 124 gridCellRect.size.height = block_size - grid_width; 125 126 for (int i = 0; i < map_width; i++) { 127 for (int j = 0; j < map_height; j++) { 128 gridCellRect.origin = NSMakePoint(mapRect.origin.x + block_size * i, mapRect.origin.y + block_size * j); 129 int color = map_colors[i * map_width + j]; 130 switch (color) { 131 case -4: break; 132 case -3: [[NSColor darkGrayColor] set]; [NSBezierPath fillRect:gridCellRect]; break; 133 case -2: [[NSColor grayColor] set]; [NSBezierPath fillRect:gridCellRect]; break; 134 case -1: [[NSColor whiteColor] set]; [NSBezierPath fillRect:gridCellRect];break; 135 default: [(NSColor*)[color_cache objectAtIndex:color] set]; [NSBezierPath fillRect:gridCellRect]; break; 136 } 137 138 if (i == selected_x && j == selected_y) { 139 // Handle selected cell preferentially to tags 140 [[NSColor greenColor] set]; 141 } else { 142 // Handle tag coloration 143 int tag = map_tags[i * map_width + j]; 144 switch (tag) { 145 case -4: continue; 146 case -3: [[NSColor darkGrayColor] set]; break; 147 case -2: [[NSColor grayColor] set]; break; 148 case -1: [[NSColor whiteColor] set]; break; 149 default: continue; 150 } 151 } 152 // Draw tag outline 153 [NSGraphicsContext saveGraphicsState]; 154 NSBezierPath* tagPath = [NSBezierPath bezierPathWithRect:gridCellRect]; 155 [tagPath setLineWidth:2.0]; 156 [tagPath setLineCapStyle:NSSquareLineCapStyle]; 157 [tagPath setClip]; 158 [tagPath stroke]; 159 [NSGraphicsContext restoreGraphicsState]; 160 } 161 } 162} 163 164 165- (BOOL) isOpaque { 166 return YES; 167} 168 169 170- (void) updateState:(Avida::CoreView::Map*)state { 171 state->Retain(); 172 173 map_width = state->GetWidth(); 174 map_height = state->GetHeight(); 175 176 map_colors = state->GetColors(); 177 num_colors = state->GetColorScale().GetScaleRange(); 178 179 map_tags = state->GetTags(); 180 181 state->Release(); 182 183 184 if (zoom < 0) { 185 NSScrollView* scrollView = [self enclosingScrollView]; 186 assert(scrollView != nil); 187 188 NSSize bounds = [scrollView bounds].size; 189 double z1 = bounds.width / map_width; 190 double z2 = bounds.height / map_height; 191 double zval = (z1 > z2) ? z2 : z1; 192 if (zval > 15.0) zval = 15.0; 193 zval = floor(zval); 194 [self setZoom:zval]; 195 } else { 196 [self setNeedsDisplay:YES]; 197 } 198} 199 200 201- (void) mouseDown:(NSEvent*)event { 202 203 if (selectionDelegate != nil) { 204 // convert the mouse-down location into the view coords 205 NSPoint clickLocation = [self convertPoint:[event locationInWindow] fromView:nil]; 206 207 CGFloat block_size = zoom; 208 209 NSPoint selectedOrg; 210 selectedOrg.x = floor(clickLocation.x / block_size); 211 selectedOrg.y = floor(clickLocation.y / block_size); 212 if (selected_x != selectedOrg.x || selected_y != selectedOrg.y) { 213 if (selectionDelegate == nil || 214 ![selectionDelegate respondsToSelector:@selector(mapView:shouldSelectObjectAtPoint:)] || 215 [selectionDelegate mapView:self shouldSelectObjectAtPoint:selectedOrg]) { 216 selected_x = selectedOrg.x; 217 selected_y = selectedOrg.y; 218 [selectionDelegate mapViewSelectionChanged:self]; 219 } 220 } 221 } 222 223} 224 225- (NSColor*) colorOfX:(int)x Y:(int)y { 226 int color = map_colors[x * map_width + y]; 227 switch (color) { 228 case -4: return [NSColor blackColor]; 229 case -3: return [NSColor darkGrayColor]; 230 case -2: return [NSColor grayColor]; 231 case -1: return [NSColor whiteColor]; 232 default: return (NSColor*)[color_cache objectAtIndex:color]; 233 } 234 return [NSColor blackColor]; 235} 236 237@synthesize zoom; 238- (void) setZoom:(double)zval { 239 zoom = round(zval); 240 241 CGFloat block_size = zoom; 242 CGFloat grid_width = (block_size > 5.0) ? 1.0 : 0.0; 243 244 NSSize mapSize; 245 mapSize.width = map_width * block_size - grid_width; 246 mapSize.height = map_height * block_size - grid_width; 247 248 [self setFrameSize:mapSize]; 249 250 [self setNeedsDisplay:YES]; 251} 252 253@synthesize selectionDelegate; 254 255 256- (NSPoint) selectedObject { 257 return NSMakePoint(selected_x, selected_y); 258} 259 260- (void) setSelectedObject:(NSPoint)point { 261 int new_x = floor(point.x); 262 int new_y = floor(point.y); 263 if (new_x < map_width && new_x >= 0 && new_y < map_height && new_y >= 0) { 264 if (new_x != selected_x || new_y != selected_y) { 265 selected_x = floor(point.x); 266 selected_y = floor(point.y); 267 [selectionDelegate mapViewSelectionChanged:self]; 268 } 269 } 270} 271 272- (void) clearSelectedObject { 273 if (selected_x != -1) { 274 selected_x = -1; 275 selected_y = -1; 276 [selectionDelegate mapViewSelectionChanged:self]; 277 } 278} 279 280 281- (void) setFrame:(NSRect)frameRect { 282 [super setFrame:frameRect]; 283} 284 285- (void) setFrameOrigin:(NSPoint)newOrigin { 286 [super setFrameOrigin:newOrigin]; 287} 288 289- (void) setFrameSize:(NSSize)newSize { 290 [super setFrameSize:newSize]; 291} 292 293- (void) setFrameRotation:(CGFloat)angle { 294 [super setFrameRotation:angle]; 295} 296 297 298@end 299