API  1.0.0
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 
26 @typedef CPTickMarkPosition
31 
32 @typedef CPLevelIndicatorStyle
37 
43 @implementation CPLevelIndicator : CPControl
44 {
45  CPLevelIndicator _levelIndicatorStyle;
46  double _minValue;
47  double _maxValue;
48  double _warningValue;
49  double _criticalValue;
50  CPTickMarkPosition _tickMarkPosition;
51  int _numberOfTickMarks;
52  int _numberOfMajorTickMarks;
53 
54  BOOL _isEditable;
55 
56  BOOL _isTracking;
57 }
58 
59 + (CPString)defaultThemeClass
60 {
61  return "level-indicator";
62 }
63 
64 + (CPDictionary)themeAttributes
65 {
66  return @{
67  @"bezel-color": [CPNull null],
68  @"color-empty": [CPNull null],
69  @"color-normal": [CPNull null],
70  @"color-warning": [CPNull null],
71  @"color-critical": [CPNull null],
72  @"spacing": 1.0,
73  };
74 }
75 
76 - (id)initWithFrame:(CGRect)aFrame
77 {
78  self = [super initWithFrame:aFrame];
79 
80  if (self)
81  {
82  _levelIndicatorStyle = CPDiscreteCapacityLevelIndicatorStyle;
83  _maxValue = 2;
84  _warningValue = 2;
85  _criticalValue = 2;
86  }
87 
88  return self;
89 }
90 
91 - (void)layoutSubviews
92 {
93  var bezelView = [self layoutEphemeralSubviewNamed:"bezel"
94  positioned:CPWindowBelow
96  // TODO Make themable.
97  [bezelView setBackgroundColor:[self valueForThemeAttribute:@"bezel-color"]];
98 
99  var segmentCount = _maxValue - _minValue;
100 
101  if (segmentCount <= 0)
102  return;
103 
104  var filledColor = [self valueForThemeAttribute:@"color-normal"],
105  value = [self doubleValue];
106 
107  if (_warningValue < _criticalValue)
108  {
109  if (value >= _criticalValue)
110  filledColor = [self valueForThemeAttribute:@"color-critical"];
111  else if (value >= _warningValue)
112  filledColor = [self valueForThemeAttribute:@"color-warning"];
113  }
114  else
115  {
116  if (value <= _criticalValue)
117  filledColor = [self valueForThemeAttribute:@"color-critical"];
118  else if (value <= _warningValue)
119  filledColor = [self valueForThemeAttribute:@"color-warning"];
120  }
121 
122 
123  for (var i = 0; i < segmentCount; i++)
124  {
125  var segmentView = [self layoutEphemeralSubviewNamed:"segment-bezel-" + i
126  positioned:CPWindowAbove
128 
129  [segmentView setBackgroundColor:(_minValue + i) < value ? filledColor : [self valueForThemeAttribute:@"color-empty"]];
130  }
131 }
132 
133 - (CPView)createEphemeralSubviewNamed:(CPString)aName
134 {
135  return [[CPView alloc] initWithFrame:CGRectMakeZero()];
136 }
137 
138 - (CGRect)rectForEphemeralSubviewNamed:(CPString)aViewName
139 {
140  // TODO Put into theme attributes.
141  var bezelHeight = 18,
142  segmentHeight = 17,
143  bounds = CGRectCreateCopy([self bounds]);
144 
145  if (aViewName == "bezel")
146  {
147  bounds.origin.y = (CGRectGetHeight(bounds) - bezelHeight) / 2.0;
148  bounds.size.height = bezelHeight;
149  return bounds;
150  }
151  else if (aViewName.indexOf("segment-bezel") === 0)
152  {
153  var segment = parseInt(aViewName.substring("segment-bezel-".length), 10),
154  segmentCount = _maxValue - _minValue;
155 
156  if (segment >= segmentCount)
157  return CGRectMakeZero();
158 
159  var basicSegmentWidth = bounds.size.width / segmentCount,
160  segmentFrame = CGRectCreateCopy([self bounds]),
161  spacing = [self valueForThemeAttribute:@"spacing"];
162 
163  segmentFrame.origin.y = (CGRectGetHeight(bounds) - bezelHeight) / 2.0;
164  segmentFrame.origin.x = FLOOR(segment * basicSegmentWidth);
165  segmentFrame.size.width = (segment == segmentCount - 1) ? bounds.size.width - segmentFrame.origin.x : FLOOR(((segment + 1) * basicSegmentWidth)) - FLOOR((segment * basicSegmentWidth)) - spacing;
166  segmentFrame.size.height = segmentHeight;
167 
168  return segmentFrame;
169  }
170 
171  return CGRectMakeZero();
172 }
173 
177 - (void)setEditable:(BOOL)shouldBeEditable
178 {
179  if (_isEditable === shouldBeEditable)
180  return;
181 
182  _isEditable = shouldBeEditable;
183 }
184 
188 - (BOOL)isEditable
189 {
190  return _isEditable;
191 }
192 
193 - (CPView)hitTest:(CGPoint)aPoint
194 {
195  // Don't swallow clicks when displayed in a table.
196  if (![self isEditable])
197  return nil;
198 
199  return [super hitTest:aPoint];
200 }
201 
202 - (void)mouseDown:(CPEvent)anEvent
203 {
204  if (![self isEditable] || ![self isEnabled])
205  return;
206 
207  [self _trackMouse:anEvent];
208 }
209 
210 - (void)_trackMouse:(CPEvent)anEvent
211 {
212  var type = [anEvent type];
213 
214  if (type == CPLeftMouseDown || type == CPLeftMouseDragged)
215  {
216  var segmentCount = _maxValue - _minValue;
217 
218  if (segmentCount <= 0)
219  return;
220 
221  var location = [self convertPoint:[anEvent locationInWindow] fromView:nil],
222  bounds = [self bounds],
223  oldValue = [self doubleValue],
224  newValue = oldValue;
225 
226  // Moving the mouse outside of the widget to the left sets it
227  // to its minimum, and moving outside on the right sets it to
228  // its maximum.
229  if (type == CPLeftMouseDragged && location.x < 0)
230  {
231  newValue = _minValue;
232  }
233  else if (type == CPLeftMouseDragged && location.x > bounds.size.width)
234  {
235  newValue = _maxValue;
236  }
237  else
238  {
239  for (var i = 0; i < segmentCount; i++)
240  {
241  var rect = [self rectForEphemeralSubviewNamed:"segment-bezel-" + i];
242 
243  // Once we're tracking the mouse, we only care about horizontal mouse movement.
244  if (location.x >= CGRectGetMinX(rect) && location.x < CGRectGetMaxX(rect))
245  {
246  newValue = (_minValue + i + 1);
247  break;
248  }
249  }
250  }
251 
252  if (newValue != oldValue)
253  [self setDoubleValue:newValue];
254 
255  // Track the mouse to support click and slide value editing.
256  _isTracking = YES;
257  [CPApp setTarget:self selector:@selector(_trackMouse:) forNextEventMatchingMask:CPLeftMouseDraggedMask | CPLeftMouseUpMask untilDate:nil inMode:nil dequeue:YES];
258 
259  if ([self isContinuous])
260  [self sendAction:[self action] to:[self target]];
261  }
262  else if (_isTracking)
263  {
264  _isTracking = NO;
265  [self sendAction:[self action] to:[self target]];
266  }
267 }
268 
269 /*
270 - (CPLevelIndicatorStyle)style;
271 - (void)setLevelIndicatorStyle:(CPLevelIndicatorStyle)style;
272 */
273 
274 - (void)setMinValue:(double)minValue
275 {
276  if (_minValue === minValue)
277  return;
278  _minValue = minValue;
279 
280  [self setNeedsLayout];
281 }
282 
283 - (void)setMaxValue:(double)maxValue
284 {
285  if (_maxValue === maxValue)
286  return;
287  _maxValue = maxValue;
288 
289  [self setNeedsLayout];
290 }
291 
292 - (void)setWarningValue:(double)warningValue
293 {
294  if (_warningValue === warningValue)
295  return;
296  _warningValue = warningValue;
297 
298  [self setNeedsLayout];
299 }
300 
301 - (void)setCriticalValue:(double)criticalValue
302 {
303  if (_criticalValue === criticalValue)
304  return;
305  _criticalValue = criticalValue;
306 
307  [self setNeedsLayout];
308 }
309 
310 /*
311 - (CPTickMarkPosition)tickMarkPosition;
312 - (void)setTickMarkPosition:(CPTickMarkPosition)position;
313 
314 - (int)numberOfTickMarks;
315 - (void)setNumberOfTickMarks:(int)count;
316 
317 - (int)numberOfMajorTickMarks;
318 - (void)setNumberOfMajorTickMarks:(int)count;
319 
320 - (double)tickMarkValueAtIndex:(int)index;
321 - (CGRect)rectOfTickMarkAtIndex:(int)index;
322 */
323 
324 @end
325 
326 var CPLevelIndicatorStyleKey = "CPLevelIndicatorStyleKey",
327  CPLevelIndicatorMinValueKey = "CPLevelIndicatorMinValueKey",
328  CPLevelIndicatorMaxValueKey = "CPLevelIndicatorMaxValueKey",
329  CPLevelIndicatorWarningValueKey = "CPLevelIndicatorWarningValueKey",
330  CPLevelIndicatorCriticalValueKey = "CPLevelIndicatorCriticalValueKey",
331  CPLevelIndicatorTickMarkPositionKey = "CPLevelIndicatorTickMarkPositionKey",
332  CPLevelIndicatorNumberOfTickMarksKey = "CPLevelIndicatorNumberOfTickMarksKey",
333  CPLevelIndicatorNumberOfMajorTickMarksKey = "CPLevelIndicatorNumberOfMajorTickMarksKey",
334  CPLevelIndicatorIsEditableKey = "CPLevelIndicatorIsEditableKey";
335 
337 
338 - (id)initWithCoder:(CPCoder)aCoder
339 {
340  self = [super initWithCoder:aCoder];
341 
342  if (self)
343  {
344  _levelIndicatorStyle = [aCoder decodeIntForKey:CPLevelIndicatorStyleKey];
345  _minValue = [aCoder decodeDoubleForKey:CPLevelIndicatorMinValueKey];
346  _maxValue = [aCoder decodeDoubleForKey:CPLevelIndicatorMaxValueKey];
347  _warningValue = [aCoder decodeDoubleForKey:CPLevelIndicatorWarningValueKey];
348  _criticalValue = [aCoder decodeDoubleForKey:CPLevelIndicatorCriticalValueKey];
349  _tickMarkPosition = [aCoder decodeIntForKey:CPLevelIndicatorTickMarkPositionKey];
350  _numberOfTickMarks = [aCoder decodeIntForKey:CPLevelIndicatorNumberOfTickMarksKey];
351  _numberOfMajorTickMarks = [aCoder decodeIntForKey:CPLevelIndicatorNumberOfMajorTickMarksKey];
352 
353  _isEditable = [aCoder decodeBoolForKey:CPLevelIndicatorIsEditableKey];
354 
355  [self setNeedsLayout];
356  [self setNeedsDisplay:YES];
357  }
358 
359  return self;
360 }
361 
362 - (void)encodeWithCoder:(CPCoder)aCoder
363 {
364  [super encodeWithCoder:aCoder];
365 
366  [aCoder encodeInt:_levelIndicatorStyle forKey:CPLevelIndicatorStyleKey];
367  [aCoder encodeDouble:_minValue forKey:CPLevelIndicatorMinValueKey];
368  [aCoder encodeDouble:_maxValue forKey:CPLevelIndicatorMaxValueKey];
369  [aCoder encodeDouble:_warningValue forKey:CPLevelIndicatorWarningValueKey];
370  [aCoder encodeDouble:_criticalValue forKey:CPLevelIndicatorCriticalValueKey];
371  [aCoder encodeInt:_tickMarkPosition forKey:CPLevelIndicatorTickMarkPositionKey];
372  [aCoder encodeInt:_numberOfTickMarks forKey:CPLevelIndicatorNumberOfTickMarksKey];
373  [aCoder encodeInt:_numberOfMajorTickMarks forKey:CPLevelIndicatorNumberOfMajorTickMarksKey];
374  [aCoder encodeBool:_isEditable forKey:CPLevelIndicatorIsEditableKey];
375 }
376 
377 @end
378 
380 
384 - (CPLevelIndicator)levelIndicatorStyle
385 {
386  return _levelIndicatorStyle;
387 }
388 
392 - (void)setLevelIndicatorStyle:(CPLevelIndicator)aValue
393 {
394  _levelIndicatorStyle = aValue;
395 }
396 
400 - (double)minValue
401 {
402  return _minValue;
403 }
404 
408 - (void)setMinValue:(double)aValue
409 {
410  _minValue = aValue;
411 }
412 
416 - (double)maxValue
417 {
418  return _maxValue;
419 }
420 
424 - (void)setMaxValue:(double)aValue
425 {
426  _maxValue = aValue;
427 }
428 
432 - (double)warningValue
433 {
434  return _warningValue;
435 }
436 
440 - (void)setWarningValue:(double)aValue
441 {
442  _warningValue = aValue;
443 }
444 
448 - (double)criticalValue
449 {
450  return _criticalValue;
451 }
452 
456 - (void)setCriticalValue:(double)aValue
457 {
458  _criticalValue = aValue;
459 }
460 
464 - (CPTickMarkPosition)tickMarkPosition
465 {
466  return _tickMarkPosition;
467 }
468 
472 - (void)setTickMarkPosition:(CPTickMarkPosition)aValue
473 {
474  _tickMarkPosition = aValue;
475 }
476 
480 - (int)numberOfTickMarks
481 {
482  return _numberOfTickMarks;
483 }
484 
488 - (void)setNumberOfTickMarks:(int)aValue
489 {
490  _numberOfTickMarks = aValue;
491 }
492 
496 - (int)numberOfMajorTickMarks
497 {
498  return _numberOfMajorTickMarks;
499 }
500 
504 - (void)setNumberOfMajorTickMarks:(int)aValue
505 {
506  _numberOfMajorTickMarks = aValue;
507 }
508 
509 @end
var CPLevelIndicatorNumberOfTickMarksKey
CPRatingLevelIndicatorStyle
var CPLevelIndicatorMaxValueKey
An object representation of nil.
Definition: CPNull.h:2
CGPoint locationInWindow()
Definition: CPEvent.j:290
CPDiscreteCapacityLevelIndicatorStyle
id initWithFrame:(CGRect aFrame)
Definition: CPControl.j:183
CPEventType type()
Definition: CPEvent.j:325
global CPApp typedef CPTickMarkPosition CPTickMarkBelow
CPView hitTest:(CGPoint aPoint)
Definition: CPView.j:1857
CPContinuousCapacityLevelIndicatorStyle
A mutable key-value pair collection.
Definition: CPDictionary.h:2
var CPLevelIndicatorCriticalValueKey
var CPLevelIndicatorTickMarkPositionKey
CGRect bounds()
Definition: CALayer.j:203
An immutable string (collection of characters).
Definition: CPString.h:2
CPNull null()
Definition: CPNull.j:51
id initWithCoder:(CPCoder aCoder)
Definition: CPControl.j:1092
void setNeedsDisplay:(BOOL aFlag)
Definition: CPView.j:2597
var CPLevelIndicatorStyleKey
var CPLevelIndicatorMinValueKey
CPTickMarkAbove
void setNeedsLayout()
Definition: CPView.j:2748
var CPLevelIndicatorNumberOfMajorTickMarksKey
double doubleValue()
Definition: CPControl.j:562
CPLeftMouseDragged
Defines methods for use when archiving & restoring (enc/decoding).
Definition: CPCoder.h:2
CPLeftMouseDown
Definition: CPEvent.h:2
var CPLevelIndicatorIsEditableKey
CPLevelIndicatorStyle CPRelevancyLevelIndicatorStyle
CPTickMarkLeft
void encodeWithCoder:(CPCoder aCoder)
Definition: CPControl.j:1121
CPView layoutEphemeralSubviewNamed:positioned:relativeToEphemeralSubviewNamed:(CPString aViewName, [positioned] CPWindowOrderingMode anOrderingMode, [relativeToEphemeralSubviewNamed] CPString relativeToViewName)
Definition: CPView.j:3407
var CPLevelIndicatorWarningValueKey
Definition: CPView.j:137
CPTickMarkRight