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