API  0.9.7
 All Classes Files Functions Variables Macros Groups Pages
CPTableHeaderView.j
Go to the documentation of this file.
1 /*
2  * CPTableHeaderView.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 
25 @class CPTableView
26 
27 @global CPApp
28 
29 
30 @implementation _CPTableColumnHeaderView : CPView
31 {
32  _CPImageAndTextView _textField;
33 }
34 
35 + (CPString)defaultThemeClass
36 {
37  return @"columnHeader";
38 }
39 
40 + (CPDictionary)themeAttributes
41 {
42  return @{
43  @"background-color": [CPNull null],
44  @"text-alignment": CPLeftTextAlignment,
45  @"line-break-mode": CPLineBreakByTruncatingTail,
46  @"text-inset": CGInsetMakeZero(),
47  @"text-color": [CPNull null],
48  @"font": [CPNull null],
49  @"text-shadow-color": [CPNull null],
50  @"text-shadow-offset": CGSizeMakeZero()
51  };
52 }
53 
54 - (id)initWithFrame:(CGRect)frame
55 {
56  self = [super initWithFrame:frame];
57 
58  if (self)
59  [self _init];
60 
61  return self;
62 }
63 
64 - (void)_init
65 {
66  _textField = [[_CPImageAndTextView alloc] initWithFrame:CGRectMakeZero()];
67 
68  [_textField setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
69 
70  [_textField setLineBreakMode:CPLineBreakByTruncatingTail];
71  [_textField setAlignment:CPLeftTextAlignment];
72  [_textField setVerticalAlignment:CPCenterVerticalTextAlignment];
73 
74  [self addSubview:_textField];
75 }
76 
77 - (void)layoutSubviews
78 {
79  [self setBackgroundColor:[self currentValueForThemeAttribute:@"background-color"]];
80 
81  var inset = [self currentValueForThemeAttribute:@"text-inset"],
82  bounds = [self bounds];
83 
84  [_textField setFrame:CGRectMake(inset.right, inset.top, bounds.size.width - inset.right - inset.left, bounds.size.height - inset.top - inset.bottom)];
85  [_textField setTextColor:[self currentValueForThemeAttribute:@"text-color"]];
86  [_textField setFont:[self currentValueForThemeAttribute:@"font"]];
87  [_textField setTextShadowColor:[self currentValueForThemeAttribute:@"text-shadow-color"]];
88  [_textField setTextShadowOffset:[self currentValueForThemeAttribute:@"text-shadow-offset"]];
89  [_textField setAlignment:[self currentValueForThemeAttribute:@"text-alignment"]];
90  [_textField setLineBreakMode:[self currentValueForThemeAttribute:@"line-break-mode"]];
91 }
92 
93 - (void)setStringValue:(CPString)string
94 {
95  [_textField setText:string];
96 }
97 
98 - (CPString)stringValue
99 {
100  return [_textField text];
101 }
102 
103 - (void)textField
104 {
105  return _textField;
106 }
107 
108 - (void)sizeToFit
109 {
110  [_textField sizeToFit];
111 }
112 
113 - (void)setFont:(CPFont)aFont
114 {
115  [self setValue:aFont forThemeAttribute:@"font"];
116 }
117 
118 - (CPFont)font
119 {
120  return [self currentValueForThemeAttribute:@"font"]
121 }
122 
123 - (void)setAlignment:(CPTextAlignment)alignment
124 {
125  [self setValue:alignment forThemeAttribute:@"text-alignment"];
126 }
127 
128 - (CPTextAlignment)alignment
129 {
130  return [self currentValueForThemeAttribute:@"text-alignment"]
131 }
132 
133 - (void)setLineBreakMode:(CPLineBreakMode)mode
134 {
135  [self setValue:mode forThemeAttribute:@"line-break-mode"];
136 }
137 
138 - (CPLineBreakMode)lineBreakMode
139 {
140  return [self currentValueForThemeAttribute:@"line-break-mode"]
141 }
142 
143 - (void)setTextColor:(CPColor)aColor
144 {
145  [self setValue:aColor forThemeAttribute:@"text-color"];
146 }
147 
148 - (CPColor)textColor
149 {
150  return [self currentValueForThemeAttribute:@"text-color"]
151 }
152 
153 - (void)setTextShadowColor:(CPColor)aColor
154 {
155  [self setValue:aColor forThemeAttribute:@"text-shadow-color"];
156 }
157 
158 - (CPColor)textShadowColor
159 {
160  return [self currentValueForThemeAttribute:@"text-shadow-color"]
161 }
162 
163 - (void)_setIndicatorImage:(CPImage)anImage
164 {
165  if (anImage)
166  {
167  [_textField setImage:anImage];
168  [_textField setImagePosition:CPImageRight];
169  }
170  else
171  {
172  [_textField setImagePosition:CPNoImage];
173  }
174 }
175 
176 @end
177 
178 var _CPTableColumnHeaderViewStringValueKey = @"_CPTableColumnHeaderViewStringValueKey",
179  _CPTableColumnHeaderViewFontKey = @"_CPTableColumnHeaderViewFontKey",
180  _CPTableColumnHeaderViewTextColorKey = @"_CPTableColumnHeaderViewTextColorKey",
181  _CPTableColumnHeaderViewTextShadowColorKey = @"_CPTableColumnHeaderViewTextShadowColorKey",
182  _CPTableColumnHeaderViewAlignmentKey = @"_CPTableColumnHeaderViewAlignmentKey",
183  _CPTableColumnHeaderViewLineBreakModeKey = @"_CPTableColumnHeaderViewLineBreakModeKey",
184  _CPTableColumnHeaderViewImageKey = @"_CPTableColumnHeaderViewImageKey";
185 
186 @implementation _CPTableColumnHeaderView (CPCoding)
187 
188 - (id)initWithCoder:(CPCoder)aCoder
189 {
190  if (self = [super initWithCoder:aCoder])
191  {
192  [self _init];
193  [self _setIndicatorImage:[aCoder decodeObjectForKey:_CPTableColumnHeaderViewImageKey]];
194  [self setStringValue:[aCoder decodeObjectForKey:_CPTableColumnHeaderViewStringValueKey]];
195  [self setFont:[aCoder decodeObjectForKey:_CPTableColumnHeaderViewFontKey]];
196  [self setTextColor:[aCoder decodeObjectForKey:_CPTableColumnHeaderViewTextColorKey]];
197  [self setTextShadowColor:[aCoder decodeObjectForKey:_CPTableColumnHeaderViewTextShadowColorKey]];
198  [self setAlignment:[aCoder decodeIntForKey:_CPTableColumnHeaderViewAlignmentKey]];
199  [self setLineBreakMode:[aCoder decodeIntForKey:_CPTableColumnHeaderViewLineBreakModeKey]];
200  }
201 
202  return self;
203 }
204 
205 - (void)encodeWithCoder:(CPCoder)aCoder
206 {
207  [super encodeWithCoder:aCoder];
208 
209  [aCoder encodeObject:[_textField text] forKey:_CPTableColumnHeaderViewStringValueKey];
210  [aCoder encodeObject:[_textField image] forKey:_CPTableColumnHeaderViewImageKey];
211  [aCoder encodeObject:[self font] forKey:_CPTableColumnHeaderViewFontKey];
212  [aCoder encodeObject:[self textColor] forKey:_CPTableColumnHeaderViewTextColorKey];
213  [aCoder encodeObject:[self textShadowColor] forKey:_CPTableColumnHeaderViewTextShadowColorKey];
214  [aCoder encodeInt:[self alignment] forKey:_CPTableColumnHeaderViewAlignmentKey];
215  [aCoder encodeInt:[self lineBreakMode] forKey:_CPTableColumnHeaderViewLineBreakModeKey];
216 }
217 
218 @end
219 
220 @implementation CPTableHeaderView : CPView
221 {
222  CGPoint _mouseDownLocation;
223  CGPoint _previousTrackingLocation;
224  int _activeColumn;
225  int _pressedColumn;
226  int _lastDragDestinationColumnIndex;
227 
228  BOOL _isResizing;
229  BOOL _isDragging;
230  BOOL _isTrackingColumn;
231  BOOL _drawsColumnLines;
232 
233  float _columnOldWidth;
234 
235  CPTableView _tableView;
236 }
237 
238 + (CPString)defaultThemeClass
239 {
240  return @"tableHeaderRow";
241 }
242 
243 + (CPDictionary)themeAttributes
244 {
245  return @{
246  @"background-color": [CPNull null],
247  @"divider-color": [CPColor grayColor],
248  @"divider-thickness": 1.0
249  };
250 }
251 
252 - (void)_init
253 {
254  _mouseDownLocation = CGPointMakeZero();
255  _previousTrackingLocation = CGPointMakeZero();
256  _activeColumn = -1;
257  _pressedColumn = -1;
258 
259  _isResizing = NO;
260  _isDragging = NO;
261  _isTrackingColumn = NO;
262  _drawsColumnLines = YES;
263 
264  _columnOldWidth = 0.0;
265 
266  [self setBackgroundColor:[self currentValueForThemeAttribute:@"background-color"]];
267 }
268 
269 - (id)initWithFrame:(CGRect)aFrame
270 {
271  self = [super initWithFrame:aFrame];
272 
273  if (self)
274  [self _init];
275 
276  return self;
277 }
278 
279 - (int)columnAtPoint:(CGPoint)aPoint
280 {
281  return [_tableView columnAtPoint:CGPointMake(aPoint.x, aPoint.y)];
282 }
283 
284 - (CGRect)headerRectOfColumn:(CPInteger)aColumnIndex
285 {
286  var headerRect = CGRectMakeCopy([self bounds]),
287  columnRect = [_tableView rectOfColumn:aColumnIndex];
288 
289  headerRect.origin.x = CGRectGetMinX(columnRect);
290  headerRect.size.width = CGRectGetWidth(columnRect);
291 
292  return headerRect;
293 }
294 
295 - (void)setDrawsColumnLines:(BOOL)aFlag
296 {
297  _drawsColumnLines = aFlag;
298 }
299 
300 - (BOOL)drawsColumnLines
301 {
302  return _drawsColumnLines;
303 }
304 
305 - (CGRect)_cursorRectForColumn:(CPInteger)column
306 {
307  if (column == -1 || !([_tableView._tableColumns[column] resizingMask] & CPTableColumnUserResizingMask))
308  return CGRectMakeZero();
309 
310  var rect = [self headerRectOfColumn:column];
311 
312  rect.origin.x = CGRectGetMaxX(rect) - 5;
313  rect.size.width = 20;
314 
315  return rect;
316 }
317 
318 - (void)_setPressedColumn:(CPInteger)column
319 {
320  if (_pressedColumn != -1)
321  {
322  var headerView = [_tableView._tableColumns[_pressedColumn] headerView];
323  [headerView unsetThemeState:CPThemeStateHighlighted];
324  }
325 
326  if (column != -1)
327  {
328  var headerView = [_tableView._tableColumns[column] headerView];
329  [headerView setThemeState:CPThemeStateHighlighted];
330 
331  if (_tableView._editingCellIndex || _tableView._editingColumn == column)
332  [[self window] makeFirstResponder:_tableView];
333  }
334 
335  _pressedColumn = column;
336 }
337 
338 - (void)mouseDown:(CPEvent)theEvent
339 {
340  [self trackMouse:theEvent];
341 }
342 
343 - (void)trackMouse:(CPEvent)theEvent
344 {
345  var type = [theEvent type],
346  currentLocation = [self convertPoint:[theEvent locationInWindow] fromView:nil];
347 
348  // Take the right columns resize tracking area into account
349  currentLocation.x -= 5.0;
350 
351  var columnIndex = [self columnAtPoint:currentLocation],
352  shouldResize = [self shouldResizeTableColumn:columnIndex at:CGPointMake(currentLocation.x + 5.0, currentLocation.y)];
353 
354  if (type === CPLeftMouseUp)
355  {
356  if (shouldResize)
357  [self stopResizingTableColumn:_activeColumn at:currentLocation];
358  else if ([self _shouldStopTrackingTableColumn:columnIndex at:currentLocation])
359  {
360  [_tableView _didClickTableColumn:columnIndex modifierFlags:[theEvent modifierFlags]];
361  [self stopTrackingTableColumn:columnIndex at:currentLocation];
362 
363  _isTrackingColumn = NO;
364  }
365 
366  [self _updateResizeCursor:[CPApp currentEvent]];
367 
368  _activeColumn = CPNotFound;
369  return;
370  }
371 
372  if (type === CPLeftMouseDown)
373  {
374  if (columnIndex === -1)
375  return;
376 
377  _mouseDownLocation = currentLocation;
378  _activeColumn = columnIndex;
379 
380  [_tableView _sendDelegateMouseDownInHeaderOfTableColumn:columnIndex];
381 
382  if (shouldResize)
383  [self startResizingTableColumn:columnIndex at:currentLocation];
384  else
385  {
386  [self startTrackingTableColumn:columnIndex at:currentLocation];
387  _isTrackingColumn = YES;
388  }
389  }
390  else if (type === CPLeftMouseDragged)
391  {
392  if (shouldResize)
393  [self continueResizingTableColumn:_activeColumn at:currentLocation];
394  else
395  {
396  if (_activeColumn === columnIndex && CGRectContainsPoint([self headerRectOfColumn:columnIndex], currentLocation))
397  {
398  if (_isTrackingColumn && _pressedColumn !== -1)
399  {
400  if (![self continueTrackingTableColumn:columnIndex at:currentLocation])
401  return; // Stop tracking the column, because it's being dragged
402  } else
403  [self startTrackingTableColumn:columnIndex at:currentLocation];
404 
405  } else if (_isTrackingColumn && _pressedColumn !== -1)
406  [self stopTrackingTableColumn:_activeColumn at:currentLocation];
407  }
408  }
409 
410  _previousTrackingLocation = currentLocation;
411  [CPApp setTarget:self selector:@selector(trackMouse:) forNextEventMatchingMask:CPLeftMouseDraggedMask | CPLeftMouseUpMask untilDate:nil inMode:nil dequeue:YES];
412 }
413 
414 - (void)startTrackingTableColumn:(CPInteger)aColumnIndex at:(CGPoint)aPoint
415 {
416  _lastDragDestinationColumnIndex = -1;
417  [self _setPressedColumn:aColumnIndex];
418 }
419 
420 - (BOOL)continueTrackingTableColumn:(CPInteger)aColumnIndex at:(CGPoint)aPoint
421 {
422  if ([self _shouldDragTableColumn:aColumnIndex at:aPoint])
423  {
424  var columnRect = [self headerRectOfColumn:aColumnIndex],
425  offset = CGPointMakeZero(),
426  view = [_tableView _dragViewForColumn:aColumnIndex event:[CPApp currentEvent] offset:offset],
427  viewLocation = CGPointMakeZero();
428 
429  viewLocation.x = ( CGRectGetMinX(columnRect) + offset.x ) + ( aPoint.x - _mouseDownLocation.x );
430  viewLocation.y = CGRectGetMinY(columnRect) + offset.y;
431 
432  [self dragView:view at:viewLocation offset:CGSizeMakeZero() event:[CPApp currentEvent]
433  pasteboard:[CPPasteboard pasteboardWithName:CPDragPboard] source:self slideBack:YES];
434 
435  return NO;
436  }
437 
438  return YES;
439 }
440 
441 - (BOOL)_shouldStopTrackingTableColumn:(CPInteger)aColumnIndex at:(CGPoint)aPoint
442 {
443  return _isTrackingColumn && _activeColumn === aColumnIndex &&
444  CGRectContainsPoint([self headerRectOfColumn:aColumnIndex], aPoint);
445 }
446 
447 - (void)stopTrackingTableColumn:(CPInteger)aColumnIndex at:(CGPoint)aPoint
448 {
449  [self _setPressedColumn:CPNotFound];
450  [self _updateResizeCursor:[CPApp currentEvent]];
451 }
452 
453 - (BOOL)_shouldDragTableColumn:(CPInteger)aColumnIndex at:(CGPoint)aPoint
454 {
455  return ABS(aPoint.x - _mouseDownLocation.x) >= 10.0 && [_tableView _sendDelegateShouldReorderColumn:aColumnIndex toColumn:-1];
456 }
457 
458 - (CGRect)_headerRectOfLastVisibleColumn
459 {
460  var tableColumns = [_tableView tableColumns],
461  columnIndex = [tableColumns count];
462 
463  while (columnIndex--)
464  {
465  var tableColumn = [tableColumns objectAtIndex:columnIndex];
466 
467  if (![tableColumn isHidden])
468  return [self headerRectOfColumn:columnIndex];
469  }
470 
471  return nil;
472 }
473 
474 - (void)_constrainDragView:(CPView)theDragView at:(CGPoint)aPoint
475 {
476  var tableColumns = [_tableView tableColumns],
477  lastColumnRect = [self _headerRectOfLastVisibleColumn],
478  activeColumnRect = [self headerRectOfColumn:_activeColumn],
479  dragWindow = [theDragView window],
480  frame = [dragWindow frame];
481 
482  // Convert the frame origin from the global coordinate system to the windows' coordinate system
483  frame.origin = [[self window] convertGlobalToBase:frame.origin];
484  // the from the window to the view
485  frame.origin = [self convertPoint:frame.origin fromView:nil];
486 
487  // This effectively clamps the value between the minimum and maximum
488  frame.origin.x = MAX(0.0, MIN(CGRectGetMinX(frame), CGRectGetMaxX(lastColumnRect) - CGRectGetWidth(activeColumnRect)));
489 
490  // Make sure the column cannot move vertically
491  frame.origin.y = CGRectGetMinY(lastColumnRect);
492 
493  // Convert the calculated origin back to the window coordinate system
494  frame.origin = [self convertPoint:frame.origin toView:nil];
495  // Then back to the global coordinate system
496  frame.origin = [[self window] convertBaseToGlobal:frame.origin];
497 
498  [dragWindow setFrame:frame];
499 }
500 
501 - (void)_moveColumn:(CPInteger)aFromIndex toColumn:(CPInteger)aToIndex
502 {
503  if ([_tableView _sendDelegateShouldReorderColumn:aFromIndex toColumn:aToIndex])
504  {
505  [_tableView moveColumn:aFromIndex toColumn:aToIndex];
506  _activeColumn = aToIndex;
507  _pressedColumn = _activeColumn;
508  }
509 }
510 
511 - (void)draggedView:(CPView)aView beganAt:(CGPoint)aPoint
512 {
513  _isDragging = YES;
514 
515  var column = [[_tableView tableColumns] objectAtIndex:_activeColumn];
516 
517  [[column headerView] setHidden:YES];
518  [_tableView _setDraggedColumn:column];
519 
520  [self setNeedsDisplay:YES];
521 }
522 
523 - (void)draggedView:(CPView)aView movedTo:(CGPoint)aPoint
524 {
525  [self _constrainDragView:aView at:aPoint];
526 
527  var dragWindow = [aView window],
528  dragWindowFrame = [dragWindow frame];
529 
530  var hoverPoint = CGPointCreateCopy(aPoint);
531 
532  if (aPoint.x < _previousTrackingLocation.x)
533  hoverPoint = CGPointMake(CGRectGetMinX(dragWindowFrame), CGRectGetMinY(dragWindowFrame));
534  else if (aPoint.x > _previousTrackingLocation.x)
535  hoverPoint = CGPointMake(CGRectGetMaxX(dragWindowFrame), CGRectGetMinY(dragWindowFrame));
536 
537  // Convert the hover point from the global coordinate system to windows' coordinate system
538  hoverPoint = [[self window] convertGlobalToBase:hoverPoint];
539  // then to the view
540  hoverPoint = [self convertPoint:hoverPoint fromView:nil];
541 
542  var hoveredColumn = [self columnAtPoint:hoverPoint];
543 
544  if (hoveredColumn !== _lastDragDestinationColumnIndex && hoveredColumn !== -1)
545  {
546  var columnRect = [self headerRectOfColumn:hoveredColumn],
547  columnCenterPoint = [self convertPoint:CGPointMake(CGRectGetMidX(columnRect), CGRectGetMidY(columnRect)) fromView:self];
548 
549  if (hoveredColumn < _activeColumn && hoverPoint.x < columnCenterPoint.x)
550  {
551  [self _moveColumn:_activeColumn toColumn:hoveredColumn];
552  _lastDragDestinationColumnIndex = hoveredColumn;
553  }
554  else if (hoveredColumn > _activeColumn && hoverPoint.x > columnCenterPoint.x)
555  {
556  [self _moveColumn:_activeColumn toColumn:hoveredColumn];
557  _lastDragDestinationColumnIndex = hoveredColumn;
558  }
559  }
560 
561  _previousTrackingLocation = aPoint;
562 }
563 
564 - (void)draggedView:(CPImage)aView endedAt:(CGPoint)aLocation operation:(CPDragOperation)anOperation
565 {
566  _isDragging = NO;
567  _isTrackingColumn = NO; // We need to do this explicitly because the mouse up section of trackMouse is never reached
568 
569  [_tableView _setDraggedColumn:nil];
570  [[[[_tableView tableColumns] objectAtIndex:_activeColumn] headerView] setHidden:NO];
571  [self stopTrackingTableColumn:_activeColumn at:aLocation];
572 
573  [self setNeedsDisplay:YES];
574 
575  [_tableView _enqueueDraggingViews];
576 }
577 
578 - (BOOL)shouldResizeTableColumn:(CPInteger)aColumnIndex at:(CGPoint)aPoint
579 {
580  if (_isResizing)
581  return YES;
582 
583  if (_isTrackingColumn)
584  return NO;
585 
586  return [_tableView allowsColumnResizing] && CGRectContainsPoint([self _cursorRectForColumn:aColumnIndex], aPoint);
587 }
588 
589 - (void)startResizingTableColumn:(CPInteger)aColumnIndex at:(CGPoint)aPoint
590 {
591  _isResizing = YES;
592 
593  var tableColumn = [[_tableView tableColumns] objectAtIndex:aColumnIndex];
594 
595  [tableColumn setDisableResizingPosting:YES];
596  [_tableView setDisableAutomaticResizing:YES];
597 }
598 
599 - (void)continueResizingTableColumn:(CPInteger)aColumnIndex at:(CGPoint)aPoint
600 {
601  var tableColumn = [[_tableView tableColumns] objectAtIndex:aColumnIndex],
602  newWidth = [tableColumn width] + aPoint.x - _previousTrackingLocation.x;
603 
604  if (newWidth < [tableColumn minWidth])
606  else if (newWidth > [tableColumn maxWidth])
608  else
609  {
610  _tableView._lastColumnShouldSnap = NO;
611  [tableColumn setWidth:newWidth];
612 
614  [self setNeedsLayout];
615  [self setNeedsDisplay:YES];
616  }
617 }
618 
619 - (void)stopResizingTableColumn:(CPInteger)aColumnIndex at:(CGPoint)aPoint
620 {
621  var tableColumn = [[_tableView tableColumns] objectAtIndex:aColumnIndex];
622  [tableColumn _postDidResizeNotificationWithOldWidth:_columnOldWidth];
623  [tableColumn setDisableResizingPosting:NO];
624  [_tableView setDisableAutomaticResizing:NO];
625 
626  _isResizing = NO;
627 }
628 
629 - (void)_updateResizeCursor:(CPEvent)theEvent
630 {
631  // never get stuck in resize cursor mode (FIXME take out when we turn on tracking rects)
632  if (![_tableView allowsColumnResizing] || ([theEvent type] === CPLeftMouseUp && ![[self window] acceptsMouseMovedEvents]))
633  {
635  return;
636  }
637 
638  var mouseLocation = [self convertPoint:[theEvent locationInWindow] fromView:nil],
639  mouseOverLocation = CGPointMake(mouseLocation.x - 5, mouseLocation.y),
640  overColumn = [self columnAtPoint:mouseOverLocation];
641 
642  if (overColumn >= 0 && CGRectContainsPoint([self _cursorRectForColumn:overColumn], mouseLocation))
643  {
644  var tableColumn = [[_tableView tableColumns] objectAtIndex:overColumn],
645  width = [tableColumn width];
646 
647  if (width == [tableColumn minWidth])
649  else if (width == [tableColumn maxWidth])
651  else
653  }
654  else
656 }
657 
658 - (void)mouseEntered:(CPEvent)theEvent
659 {
660  [self _updateResizeCursor:theEvent];
661 }
662 
663 - (void)mouseMoved:(CPEvent)theEvent
664 {
665  [self _updateResizeCursor:theEvent];
666 }
667 
668 - (void)mouseExited:(CPEvent)theEvent
669 {
670  // FIXME: we should use CPCursor push/pop (if previous currentCursor != arrow).
672 }
673 
674 - (void)layoutSubviews
675 {
676  var tableColumns = [_tableView tableColumns],
677  count = [tableColumns count],
678  lineThickness = [self currentValueForThemeAttribute:@"divider-thickness"];
679 
680  for (var i = 0; i < count; i++)
681  {
682  var column = [tableColumns objectAtIndex:i],
683  headerView = [column headerView],
684  frame = [self headerRectOfColumn:i];
685 
686  // Make space for the gridline on the right.
687  frame.origin.x -= 0.5;
688  frame.size.width -= lineThickness;
689  frame.size.height -= 0.5;
690  // Note: we're not adding in intercell spacing here. This setting only affects the regular
691  // table cell data views, not the header. Verified in Cocoa on March 29th, 2011.
692 
693  [headerView setFrame:frame];
694 
695  if ([headerView superview] != self)
696  [self addSubview:headerView];
697  }
698 
699  [self setBackgroundColor:[self currentValueForThemeAttribute:@"background-color"]];
700 }
701 
702 - (void)drawRect:(CGRect)aRect
703 {
704  if (!_tableView || ![self drawsColumnLines])
705  return;
706 
708  exposedColumnIndexes = [_tableView columnIndexesInRect:aRect],
709  columnsArray = [],
710  tableColumns = [_tableView tableColumns],
711  exposedTableColumns = _tableView._exposedColumns,
712  firstIndex = [exposedTableColumns firstIndex],
713  exposedRange = CPMakeRange(firstIndex, [exposedTableColumns lastIndex] - firstIndex + 1),
714  lineThickness = [self currentValueForThemeAttribute:@"divider-thickness"];
715 
716  CGContextSetLineWidth(context, lineThickness);
717  CGContextSetStrokeColor(context, [self currentValueForThemeAttribute:@"divider-color"]);
718 
719  [exposedColumnIndexes getIndexes:columnsArray maxCount:-1 inIndexRange:exposedRange];
720 
721  var columnArrayIndex = 0,
722  columnArrayCount = columnsArray.length,
723  columnMaxX;
724 
725  CGContextBeginPath(context);
726 
727  for (; columnArrayIndex < columnArrayCount; columnArrayIndex++)
728  {
729  // grab each column rect and add vertical lines
730  var columnIndex = columnsArray[columnArrayIndex],
731  columnToStroke = [self headerRectOfColumn:columnIndex];
732 
733  columnMaxX = CGRectGetMaxX(columnToStroke);
734 
735  CGContextMoveToPoint(context, FLOOR(columnMaxX) - 0.5 * lineThickness, ROUND(CGRectGetMinY(columnToStroke)));
736  CGContextAddLineToPoint(context, FLOOR(columnMaxX) - 0.5 * lineThickness, ROUND(CGRectGetMaxY(columnToStroke)) - 1.0);
737  }
738 
739  CGContextClosePath(context);
740  CGContextStrokePath(context);
741 
742  /*if (_isDragging)
743  {
744  CGContextSetFillColor(context, [CPColor grayColor]);
745  CGContextFillRect(context, [self headerRectOfColumn:_activeColumn])
746  }*/
747 }
748 
749 @end
750 
751 var CPTableHeaderViewTableViewKey = @"CPTableHeaderViewTableViewKey",
752  CPTableHeaderViewDrawsColumnLines = @"CPTableHeaderViewDrawsColumnLines";
753 
754 @implementation CPTableHeaderView (CPCoding)
755 
756 - (id)initWithCoder:(CPCoder)aCoder
757 {
758  if (self = [super initWithCoder:aCoder])
759  {
760  [self _init];
761  _tableView = [aCoder decodeObjectForKey:CPTableHeaderViewTableViewKey];
762 
763  // FIX ME: Take this out before 1.0
764  if ([aCoder containsValueForKey:CPTableHeaderViewDrawsColumnLines])
765  _drawsColumnLines = [aCoder decodeBoolForKey:CPTableHeaderViewDrawsColumnLines];
766  else
767  {
768  _drawsColumnLines = YES;
769  CPLog.warn("The tableview header being decoded is using an old cib. Please run Nib2Cib.");
770  }
771  }
772 
773  return self;
774 }
775 
776 - (void)encodeWithCoder:(CPCoder)aCoder
777 {
778  [super encodeWithCoder:aCoder];
779  [aCoder encodeObject:_tableView forKey:CPTableHeaderViewTableViewKey];
780  [aCoder encodeBool:_drawsColumnLines forKey:CPTableHeaderViewDrawsColumnLines];
781 }
782 
783 @end
784 
785 @implementation CPTableHeaderView (CPSynthesizedAccessors)
786 
790 - (CPTableView)tableView
791 {
792  return _tableView;
793 }
794 
798 - (void)setTableView:(CPTableView)aValue
799 {
800  _tableView = aValue;
801 }
802 
803 @end