API  0.9.7
 All Classes Files Functions Variables Macros Groups Pages
CPLevelIndicator.j
Go to the documentation of this file.
1 /*
2  * CPLevelIndicator.j
3  * AppKit
4  *
5  * Created by Alexander Ljungberg.
6  * Copyright 2011, WireLoad Inc.
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2.1 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21  */
22 
23 
24 @global CPApp
25 
30 
35 
42 @implementation CPLevelIndicator : CPControl
43 {
44  CPLevelIndicator _levelIndicatorStyle;
45  double _minValue;
46  double _maxValue;
47  double _warningValue;
48  double _criticalValue;
49  CPTickMarkPosition _tickMarkPosition;
50  int _numberOfTickMarks;
51  int _numberOfMajorTickMarks;
52 
53  BOOL _isEditable;
54 
55  BOOL _isTracking;
56 }
57 
58 + (CPString)defaultThemeClass
59 {
60  return "level-indicator";
61 }
62 
63 + (CPDictionary)themeAttributes
64 {
65  return @{
66  @"bezel-color": [CPNull null],
67  @"color-empty": [CPNull null],
68  @"color-normal": [CPNull null],
69  @"color-warning": [CPNull null],
70  @"color-critical": [CPNull null],
71  @"spacing": 1.0,
72  };
73 }
74 
75 - (id)initWithFrame:(CGRect)aFrame
76 {
77  self = [super initWithFrame:aFrame];
78 
79  if (self)
80  {
81  _levelIndicatorStyle = CPDiscreteCapacityLevelIndicatorStyle;
82  _maxValue = 2;
83  _warningValue = 2;
84  _criticalValue = 2;
85  }
86 
87  return self;
88 }
89 
90 - (void)layoutSubviews
91 {
92  var bezelView = [self layoutEphemeralSubviewNamed:"bezel"
93  positioned:CPWindowBelow
94  relativeToEphemeralSubviewNamed:nil];
95  // TODO Make themable.
96  [bezelView setBackgroundColor:[self valueForThemeAttribute:@"bezel-color"]];
97 
98  var segmentCount = _maxValue - _minValue;
99 
100  if (segmentCount <= 0)
101  return;
102 
103  var filledColor = [self valueForThemeAttribute:@"color-normal"],
104  value = [self doubleValue];
105 
106  if (value <= _criticalValue)
107  filledColor = [self valueForThemeAttribute:@"color-critical"];
108  else if (value <= _warningValue)
109  filledColor = [self valueForThemeAttribute:@"color-warning"];
110 
111  for (var i = 0; i < segmentCount; i++)
112  {
113  var segmentView = [self layoutEphemeralSubviewNamed:"segment-bezel-" + i
114  positioned:CPWindowAbove
115  relativeToEphemeralSubviewNamed:bezelView];
116 
117  [segmentView setBackgroundColor:(_minValue + i) < value ? filledColor : [self valueForThemeAttribute:@"color-empty"]];
118  }
119 }
120 
121 - (CPView)createEphemeralSubviewNamed:(CPString)aName
122 {
123  return [[CPView alloc] initWithFrame:CGRectMakeZero()];
124 }
125 
126 - (CGRect)rectForEphemeralSubviewNamed:(CPString)aViewName
127 {
128  // TODO Put into theme attributes.
129  var bezelHeight = 18,
130  segmentHeight = 17,
131  bounds = CGRectCreateCopy([self bounds]);
132 
133  if (aViewName == "bezel")
134  {
135  bounds.origin.y = (CGRectGetHeight(bounds) - bezelHeight) / 2.0;
136  bounds.size.height = bezelHeight;
137  return bounds;
138  }
139  else if (aViewName.indexOf("segment-bezel") === 0)
140  {
141  var segment = parseInt(aViewName.substring("segment-bezel-".length), 10),
142  segmentCount = _maxValue - _minValue;
143 
144  if (segment >= segmentCount)
145  return CGRectMakeZero();
146 
147  var basicSegmentWidth = bounds.size.width / segmentCount,
148  segmentFrame = CGRectCreateCopy([self bounds]),
149  spacing = [self valueForThemeAttribute:@"spacing"];
150 
151  segmentFrame.origin.y = (CGRectGetHeight(bounds) - bezelHeight) / 2.0;
152  segmentFrame.origin.x = FLOOR(segment * basicSegmentWidth);
153  segmentFrame.size.width = (segment == segmentCount - 1) ? bounds.size.width - segmentFrame.origin.x : FLOOR(((segment + 1) * basicSegmentWidth)) - FLOOR((segment * basicSegmentWidth)) - spacing;
154  segmentFrame.size.height = segmentHeight;
155 
156  return segmentFrame;
157  }
158 
159  return CGRectMakeZero();
160 }
161 
165 - (void)setEditable:(BOOL)shouldBeEditable
166 {
167  if (_isEditable === shouldBeEditable)
168  return;
169 
170  _isEditable = shouldBeEditable;
171 }
172 
176 - (BOOL)isEditable
177 {
178  return _isEditable;
179 }
180 
181 - (CPView)hitTest:(CGPoint)aPoint
182 {
183  // Don't swallow clicks when displayed in a table.
184  if (![self isEditable])
185  return nil;
186 
187  return [super hitTest:aPoint];
188 }
189 
190 - (void)mouseDown:(CPEvent)anEvent
191 {
192  if (![self isEditable] || ![self isEnabled])
193  return;
194 
195  [self _trackMouse:anEvent];
196 }
197 
198 - (void)_trackMouse:(CPEvent)anEvent
199 {
200  var type = [anEvent type];
201 
202  if (type == CPLeftMouseDown || type == CPLeftMouseDragged)
203  {
204  var segmentCount = _maxValue - _minValue;
205 
206  if (segmentCount <= 0)
207  return;
208 
209  var location = [self convertPoint:[anEvent locationInWindow] fromView:nil],
210  bounds = [self bounds],
211  oldValue = [self doubleValue],
212  newValue = oldValue;
213 
214  // Moving the mouse outside of the widget to the left sets it
215  // to its minimum, and moving outside on the right sets it to
216  // its maximum.
217  if (type == CPLeftMouseDragged && location.x < 0)
218  {
219  newValue = _minValue;
220  }
221  else if (type == CPLeftMouseDragged && location.x > bounds.size.width)
222  {
223  newValue = _maxValue;
224  }
225  else
226  {
227  for (var i = 0; i < segmentCount; i++)
228  {
229  var rect = [self rectForEphemeralSubviewNamed:"segment-bezel-" + i];
230 
231  // Once we're tracking the mouse, we only care about horizontal mouse movement.
232  if (location.x >= CGRectGetMinX(rect) && location.x < CGRectGetMaxX(rect))
233  {
234  newValue = (_minValue + i + 1);
235  break;
236  }
237  }
238  }
239 
240  if (newValue != oldValue)
241  [self setDoubleValue:newValue];
242 
243  // Track the mouse to support click and slide value editing.
244  _isTracking = YES;
245  [CPApp setTarget:self selector:@selector(_trackMouse:) forNextEventMatchingMask:CPLeftMouseDraggedMask | CPLeftMouseUpMask untilDate:nil inMode:nil dequeue:YES];
246 
247  if ([self isContinuous])
248  [self sendAction:[self action] to:[self target]];
249  }
250  else if (_isTracking)
251  {
252  _isTracking = NO;
253  [self sendAction:[self action] to:[self target]];
254  }
255 }
256 
257 /*
258 - (CPLevelIndicatorStyle)style;
259 - (void)setLevelIndicatorStyle:(CPLevelIndicatorStyle)style;
260 */
261 
262 - (void)setMinValue:(double)minValue
263 {
264  if (_minValue === minValue)
265  return;
266  _minValue = minValue;
267 
268  [self setNeedsLayout];
269 }
270 
271 - (void)setMaxValue:(double)maxValue
272 {
273  if (_maxValue === maxValue)
274  return;
275  _maxValue = maxValue;
276 
277  [self setNeedsLayout];
278 }
279 
280 - (void)setWarningValue:(double)warningValue;
281 {
282  if (_warningValue === warningValue)
283  return;
284  _warningValue = warningValue;
285 
286  [self setNeedsLayout];
287 }
288 
289 - (void)setCriticalValue:(double)criticalValue;
290 {
291  if (_criticalValue === criticalValue)
292  return;
293  _criticalValue = criticalValue;
294 
295  [self setNeedsLayout];
296 }
297 
298 /*
299 - (CPTickMarkPosition)tickMarkPosition;
300 - (void)setTickMarkPosition:(CPTickMarkPosition)position;
301 
302 - (int)numberOfTickMarks;
303 - (void)setNumberOfTickMarks:(int)count;
304 
305 - (int)numberOfMajorTickMarks;
306 - (void)setNumberOfMajorTickMarks:(int)count;
307 
308 - (double)tickMarkValueAtIndex:(int)index;
309 - (CGRect)rectOfTickMarkAtIndex:(int)index;
310 */
311 
312 @end
313 
314 var CPLevelIndicatorStyleKey = "CPLevelIndicatorStyleKey",
315  CPLevelIndicatorMinValueKey = "CPLevelIndicatorMinValueKey",
316  CPLevelIndicatorMaxValueKey = "CPLevelIndicatorMaxValueKey",
317  CPLevelIndicatorWarningValueKey = "CPLevelIndicatorWarningValueKey",
318  CPLevelIndicatorCriticalValueKey = "CPLevelIndicatorCriticalValueKey",
319  CPLevelIndicatorTickMarkPositionKey = "CPLevelIndicatorTickMarkPositionKey",
320  CPLevelIndicatorNumberOfTickMarksKey = "CPLevelIndicatorNumberOfTickMarksKey",
321  CPLevelIndicatorNumberOfMajorTickMarksKey = "CPLevelIndicatorNumberOfMajorTickMarksKey",
322  CPLevelIndicatorIsEditableKey = "CPLevelIndicatorIsEditableKey";
323 
325 
326 - (id)initWithCoder:(CPCoder)aCoder
327 {
328  self = [super initWithCoder:aCoder];
329 
330  if (self)
331  {
332  _levelIndicatorStyle = [aCoder decodeIntForKey:CPLevelIndicatorStyleKey];
333  _minValue = [aCoder decodeDoubleForKey:CPLevelIndicatorMinValueKey];
334  _maxValue = [aCoder decodeDoubleForKey:CPLevelIndicatorMaxValueKey];
335  _warningValue = [aCoder decodeDoubleForKey:CPLevelIndicatorWarningValueKey];
336  _criticalValue = [aCoder decodeDoubleForKey:CPLevelIndicatorCriticalValueKey];
337  _tickMarkPosition = [aCoder decodeIntForKey:CPLevelIndicatorTickMarkPositionKey];
338  _numberOfTickMarks = [aCoder decodeIntForKey:CPLevelIndicatorNumberOfTickMarksKey];
339  _numberOfMajorTickMarks = [aCoder decodeIntForKey:CPLevelIndicatorNumberOfMajorTickMarksKey];
340 
341  _isEditable = [aCoder decodeBoolForKey:CPLevelIndicatorIsEditableKey];
342 
343  [self setNeedsLayout];
344  [self setNeedsDisplay:YES];
345  }
346 
347  return self;
348 }
349 
350 - (void)encodeWithCoder:(CPCoder)aCoder
351 {
352  [super encodeWithCoder:aCoder];
353 
354  [aCoder encodeInt:_levelIndicatorStyle forKey:CPLevelIndicatorStyleKey];
355  [aCoder encodeDouble:_minValue forKey:CPLevelIndicatorMinValueKey];
356  [aCoder encodeDouble:_maxValue forKey:CPLevelIndicatorMaxValueKey];
357  [aCoder encodeDouble:_warningValue forKey:CPLevelIndicatorWarningValueKey];
358  [aCoder encodeDouble:_criticalValue forKey:CPLevelIndicatorCriticalValueKey];
359  [aCoder encodeInt:_tickMarkPosition forKey:CPLevelIndicatorTickMarkPositionKey];
360  [aCoder encodeInt:_numberOfTickMarks forKey:CPLevelIndicatorNumberOfTickMarksKey];
361  [aCoder encodeInt:_numberOfMajorTickMarks forKey:CPLevelIndicatorNumberOfMajorTickMarksKey];
362  [aCoder encodeBool:_isEditable forKey:CPLevelIndicatorIsEditableKey];
363 }
364 
365 @end
366 
368 
372 - (CPLevelIndicator)levelIndicatorStyle
373 {
374  return _levelIndicatorStyle;
375 }
376 
380 - (void)setLevelIndicatorStyle:(CPLevelIndicator)aValue
381 {
382  _levelIndicatorStyle = aValue;
383 }
384 
388 - (double)minValue
389 {
390  return _minValue;
391 }
392 
396 - (void)setMinValue:(double)aValue
397 {
398  _minValue = aValue;
399 }
400 
404 - (double)maxValue
405 {
406  return _maxValue;
407 }
408 
412 - (void)setMaxValue:(double)aValue
413 {
414  _maxValue = aValue;
415 }
416 
420 - (double)warningValue
421 {
422  return _warningValue;
423 }
424 
428 - (void)setWarningValue:(double)aValue
429 {
430  _warningValue = aValue;
431 }
432 
436 - (double)criticalValue
437 {
438  return _criticalValue;
439 }
440 
444 - (void)setCriticalValue:(double)aValue
445 {
446  _criticalValue = aValue;
447 }
448 
452 - (CPTickMarkPosition)tickMarkPosition
453 {
454  return _tickMarkPosition;
455 }
456 
460 - (void)setTickMarkPosition:(CPTickMarkPosition)aValue
461 {
462  _tickMarkPosition = aValue;
463 }
464 
468 - (int)numberOfTickMarks
469 {
470  return _numberOfTickMarks;
471 }
472 
476 - (void)setNumberOfTickMarks:(int)aValue
477 {
478  _numberOfTickMarks = aValue;
479 }
480 
484 - (int)numberOfMajorTickMarks
485 {
486  return _numberOfMajorTickMarks;
487 }
488 
492 - (void)setNumberOfMajorTickMarks:(int)aValue
493 {
494  _numberOfMajorTickMarks = aValue;
495 }
496 
497 @end