API  0.9.6
 All Classes Files Functions Variables Macros Groups Pages
CPBox.j
Go to the documentation of this file.
1 /*
2  * CPBox.j
3  * AppKit
4  *
5  * Created by Ross Boucher.
6  * Copyright 2009, 280 North, 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 // CPBoxType
30 
31 // CPBorderType
36 
37 // CPTitlePosition
40 CPAtTop = 2;
45 
46 
53 @implementation CPBox : CPView
54 {
55  CPBoxType _boxType;
56  CPBorderType _borderType;
57 
58  CPColor _borderColor;
59  CPColor _fillColor;
60 
61  float _cornerRadius;
62  float _borderWidth;
63 
64  CPSize _contentMargin;
65  CPView _contentView;
66 
67  CPString _title;
68  int _titlePosition;
69  CPTextField _titleView;
70 }
71 
72 + (id)boxEnclosingView:(CPView)aView
73 {
74  var box = [[self alloc] initWithFrame:CGRectMakeZero()],
75  enclosingView = [aView superview];
76 
77  [box setAutoresizingMask:[aView autoresizingMask]];
78  [box setFrameFromContentFrame:[aView frame]];
79 
80  [enclosingView replaceSubview:aView with:box];
81 
82  [box setContentView:aView];
83 
84  return box;
85 }
86 
87 - (id)initWithFrame:(CPRect)frameRect
88 {
89  self = [super initWithFrame:frameRect];
90 
91  if (self)
92  {
93  _borderType = CPBezelBorder;
94  _fillColor = [CPColor clearColor];
95  _borderColor = [CPColor blackColor];
96 
97  _borderWidth = 1.0;
98  _contentMargin = CGSizeMake(0.0, 0.0);
99 
100  _titlePosition = CPNoTitle;
101  _titleView = [CPTextField labelWithTitle:@""];
102 
103  _contentView = [[CPView alloc] initWithFrame:[self bounds]];
104  [_contentView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
105 
106  [self setAutoresizesSubviews:YES];
107  [self addSubview:_contentView];
108  }
109 
110  return self;
111 }
112 
113 // Configuring Boxes
114 
120 - (CPRect)borderRect
121 {
122  return [self bounds];
123 }
124 
137 - (CPBorderType)borderType
138 {
139  return _borderType;
140 }
141 
142 
155 - (void)setBorderType:(CPBorderType)aBorderType
156 {
157  if (_borderType === aBorderType)
158  return;
159 
160  _borderType = aBorderType;
161  [self setNeedsDisplay:YES];
162 }
163 
179 - (CPBoxType)boxType
180 {
181  return _boxType;
182 }
183 
199 - (void)setBoxType:(CPBoxType)aBoxType
200 {
201  if (_boxType === aBoxType)
202  return;
203 
204  _boxType = aBoxType;
205  [self setNeedsDisplay:YES];
206 }
207 
208 - (CPColor)borderColor
209 {
210  return _borderColor;
211 }
212 
213 - (void)setBorderColor:(CPColor)color
214 {
215  if ([color isEqual:_borderColor])
216  return;
217 
218  _borderColor = color;
219  [self setNeedsDisplay:YES];
220 }
221 
222 - (float)borderWidth
223 {
224  return _borderWidth;
225 }
226 
227 - (void)setBorderWidth:(float)width
228 {
229  if (width === _borderWidth)
230  return;
231 
232  _borderWidth = width;
233  [self setNeedsDisplay:YES];
234 }
235 
236 - (float)cornerRadius
237 {
238  return _cornerRadius;
239 }
240 
241 - (void)setCornerRadius:(float)radius
242 {
243  if (radius === _cornerRadius)
244  return;
245 
246  _cornerRadius = radius;
247  [self setNeedsDisplay:YES];
248 }
249 
250 - (CPColor)fillColor
251 {
252  return _fillColor;
253 }
254 
255 - (void)setFillColor:(CPColor)color
256 {
257  if ([color isEqual:_fillColor])
258  return;
259 
260  _fillColor = color;
261  [self setNeedsDisplay:YES];
262 }
263 
264 - (CPView)contentView
265 {
266  return _contentView;
267 }
268 
269 - (void)setContentView:(CPView)aView
270 {
271  if (aView === _contentView)
272  return;
273 
274  [aView setFrame:CGRectInset([self bounds], _contentMargin.width + _borderWidth, _contentMargin.height + _borderWidth)];
275  [aView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
276  [self replaceSubview:_contentView with:aView];
277 
278  _contentView = aView;
279 }
280 
281 - (CPSize)contentViewMargins
282 {
283  return _contentMargin;
284 }
285 
286 - (void)setContentViewMargins:(CPSize)size
287 {
288  if (size.width < 0 || size.height < 0)
289  [CPException raise:CPGenericException reason:@"Margins must be positive"];
290 
291  _contentMargin = CGSizeMakeCopy(size);
292  [self setNeedsDisplay:YES];
293 }
294 
295 - (void)setFrameFromContentFrame:(CPRect)aRect
296 {
297  var offset = [self _titleHeightOffset];
298 
299  [self setFrame:CGRectInset(aRect, -(_contentMargin.width + _borderWidth), -(_contentMargin.height + offset[0] + _borderWidth))];
300 
301  [self setNeedsDisplay:YES];
302 }
303 
304 - (void)setTitle:(CPString)aTitle
305 {
306  if (aTitle == _title)
307  return;
308 
309  _title = aTitle;
310 
311  [self _manageTitlePositioning];
312 }
313 
314 - (void)setTitlePosition:(int)aTitlePotisition
315 {
316  if (aTitlePotisition == _titlePosition)
317  return;
318 
319  _titlePosition = aTitlePotisition;
320 
321  [self _manageTitlePositioning];
322 }
323 
324 - (CPFont)titleFont
325 {
326  return [_titleView font];
327 }
328 
329 - (void)setTitleFont:(CPFont)aFont
330 {
331  [_titleView setFont:aFont];
332 }
333 
339 - (CPTextField)titleView
340 {
341  return _titleView;
342 }
343 
344 - (void)_manageTitlePositioning
345 {
346  if (_titlePosition == CPNoTitle)
347  {
348  [_titleView removeFromSuperview];
349  [self setNeedsDisplay:YES];
350  return;
351  }
352 
353  [_titleView setStringValue:_title];
354  [_titleView sizeToFit];
355  [self addSubview:_titleView];
356 
357  switch (_titlePosition)
358  {
359  case CPAtTop:
360  case CPAboveTop:
361  case CPBelowTop:
362  [_titleView setFrameOrigin:CPPointMake(5.0, 0.0)];
363  [_titleView setAutoresizingMask:CPViewNotSizable];
364  break;
365 
366  case CPAboveBottom:
367  case CPAtBottom:
368  case CPBelowBottom:
369  var h = [_titleView frameSize].height;
370  [_titleView setFrameOrigin:CPPointMake(5.0, [self frameSize].height - h)];
371  [_titleView setAutoresizingMask:CPViewMinYMargin];
372  break;
373  }
374 
375  [self sizeToFit];
376  [self setNeedsDisplay:YES];
377 }
378 
379 - (void)sizeToFit
380 {
381  var contentFrame = [_contentView frame],
382  offset = [self _titleHeightOffset];
383 
384  if (!contentFrame)
385  return;
386 
387  [_contentView setAutoresizingMask:CPViewNotSizable];
388  [self setFrameSize:CGSizeMake(contentFrame.size.width + _contentMargin.width * 2,
389  contentFrame.size.height + _contentMargin.height * 2 + offset[0])];
390  [_contentView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
391 
392  [_contentView setFrameOrigin:CGPointMake(_contentMargin.width, _contentMargin.height + offset[1])];
393 }
394 
395 - (float)_titleHeightOffset
396 {
397  if (_titlePosition == CPNoTitle)
398  return [0.0, 0.0];
399 
400  switch (_titlePosition)
401  {
402  case CPAtTop:
403  return [[_titleView frameSize].height, [_titleView frameSize].height];
404 
405  case CPAtBottom:
406  return [[_titleView frameSize].height, 0.0];
407 
408  default:
409  return [0.0, 0.0];
410  }
411 }
412 
413 - (void)drawRect:(CPRect)rect
414 {
415  if (_borderType === CPNoBorder)
416  return;
417 
418  var bounds = CGRectMakeCopy([self bounds]);
419 
420  switch (_boxType)
421  {
422  case CPBoxSeparator:
423  // NSBox does not include a horizontal flag for the separator type. We have to determine
424  // the type of separator to draw by the width and height of the frame.
425  if (CGRectGetWidth(bounds) === 5.0)
426  return [self _drawVerticalSeperatorInRect:bounds];
427  else if (CGRectGetHeight(bounds) === 5.0)
428  return [self _drawHorizontalSeperatorInRect:bounds];
429 
430  break;
431  }
432 
433  if (_titlePosition == CPAtTop)
434  {
435  bounds.origin.y += [_titleView frameSize].height;
436  bounds.size.height -= [_titleView frameSize].height;
437  }
438  if (_titlePosition == CPAtBottom)
439  {
440  bounds.size.height -= [_titleView frameSize].height;
441  }
442 
443  switch (_borderType)
444  {
445  case CPBezelBorder:
446  [self _drawBezelBorderInRect:bounds];
447  break;
448 
449  default:
450  case CPLineBorder:
451  [self _drawLineBorderInRect:bounds];
452  break;
453  }
454 }
455 
456 - (void)_drawHorizontalSeperatorInRect:(CGRect)aRect
457 {
459 
460  CGContextSetStrokeColor(context, [self borderColor]);
461  CGContextSetLineWidth(context, 1.0);
462 
463  CGContextMoveToPoint(context, CGRectGetMinX(aRect), CGRectGetMinY(aRect) + 0.5);
464  CGContextAddLineToPoint(context, CGRectGetWidth(aRect), CGRectGetMinY(aRect) + 0.5);
465  CGContextStrokePath(context);
466 }
467 
468 - (void)_drawVerticalSeperatorInRect:(CGRect)aRect
469 {
471 
472  CGContextSetStrokeColor(context, [self borderColor]);
473  CGContextSetLineWidth(context, 1.0);
474 
475  CGContextMoveToPoint(context, CGRectGetMinX(aRect) + 0.5, CGRectGetMinY(aRect));
476  CGContextAddLineToPoint(context, CGRectGetMinX(aRect) + 0.5, CGRectGetHeight(aRect));
477  CGContextStrokePath(context);
478 }
479 
480 - (void)_drawBezelBorderInRect:(CGRect)aRect
481 {
483  sides = [CPMinYEdge, CPMaxXEdge, CPMaxYEdge, CPMinXEdge],
484  sideGray = 190.0 / 255.0,
485  grays = [142.0 / 255.0, sideGray, sideGray, sideGray],
486  borderWidth = _borderWidth;
487 
488  while (borderWidth--)
489  aRect = CPDrawTiledRects(aRect, aRect, sides, grays);
490 
491  CGContextSetFillColor(context, [self fillColor]);
492  CGContextFillRect(context, aRect);
493 }
494 
495 - (void)_drawLineBorderInRect:(CGRect)aRect
496 {
498 
499  aRect = CGRectInset(aRect, _borderWidth / 2.0, _borderWidth / 2.0);
500 
501  CGContextSetFillColor(context, [self fillColor]);
502  CGContextSetStrokeColor(context, [self borderColor]);
503 
504  CGContextSetLineWidth(context, _borderWidth);
505  CGContextFillRoundedRectangleInRect(context, aRect, _cornerRadius, YES, YES, YES, YES);
506  CGContextStrokeRoundedRectangleInRect(context, aRect, _cornerRadius, YES, YES, YES, YES);
507 }
508 
509 @end
510 
511 var CPBoxTypeKey = @"CPBoxTypeKey",
512  CPBoxBorderTypeKey = @"CPBoxBorderTypeKey",
513  CPBoxBorderColorKey = @"CPBoxBorderColorKey",
514  CPBoxFillColorKey = @"CPBoxFillColorKey",
515  CPBoxCornerRadiusKey = @"CPBoxCornerRadiusKey",
516  CPBoxBorderWidthKey = @"CPBoxBorderWidthKey",
517  CPBoxContentMarginKey = @"CPBoxContentMarginKey",
518  CPBoxTitle = @"CPBoxTitle",
519  CPBoxTitlePosition = @"CPBoxTitlePosition",
520  CPBoxTitleView = @"CPBoxTitleView";
521 
522 @implementation CPBox (CPCoding)
523 
524 - (id)initWithCoder:(CPCoder)aCoder
525 {
526  self = [super initWithCoder:aCoder];
527 
528  if (self)
529  {
530  _boxType = [aCoder decodeIntForKey:CPBoxTypeKey];
531  _borderType = [aCoder decodeIntForKey:CPBoxBorderTypeKey];
532 
533  _borderColor = [aCoder decodeObjectForKey:CPBoxBorderColorKey];
534  _fillColor = [aCoder decodeObjectForKey:CPBoxFillColorKey];
535 
536  _cornerRadius = [aCoder decodeFloatForKey:CPBoxCornerRadiusKey];
537  _borderWidth = [aCoder decodeFloatForKey:CPBoxBorderWidthKey];
538 
539  _contentMargin = [aCoder decodeSizeForKey:CPBoxContentMarginKey];
540 
541  _title = [aCoder decodeObjectForKey:CPBoxTitle];
542  _titlePosition = [aCoder decodeIntForKey:CPBoxTitlePosition];
543  _titleView = [aCoder decodeObjectForKey:CPBoxTitleView] || [CPTextField labelWithTitle:_title];
544 
545  _contentView = [self subviews][0];
546 
547  [self setAutoresizesSubviews:YES];
548  [_contentView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
549 
550  [self _manageTitlePositioning];
551  }
552 
553  return self;
554 }
555 
556 - (void)encodeWithCoder:(CPCoder)aCoder
557 {
558  [super encodeWithCoder:aCoder];
559 
560  [aCoder encodeInt:_boxType forKey:CPBoxTypeKey];
561  [aCoder encodeInt:_borderType forKey:CPBoxBorderTypeKey];
562 
563  [aCoder encodeObject:_borderColor forKey:CPBoxBorderColorKey];
564  [aCoder encodeObject:_fillColor forKey:CPBoxFillColorKey];
565 
566  [aCoder encodeFloat:_cornerRadius forKey:CPBoxCornerRadiusKey];
567  [aCoder encodeFloat:_borderWidth forKey:CPBoxBorderWidthKey];
568 
569  [aCoder encodeObject:_title forKey:CPBoxTitle];
570  [aCoder encodeInt:_titlePosition forKey:CPBoxTitlePosition];
571  [aCoder encodeObject:_titleView forKey:CPBoxTitleView];
572 
573  [aCoder encodeSize:_contentMargin forKey:CPBoxContentMarginKey];
574 }
575 
576 @end
577 
579 
583 - (CPString)title
584 {
585  return _title;
586 }
587 
591 - (int)titlePosition
592 {
593  return _titlePosition;
594 }
595 
596 @end