PageRenderTime 26ms CodeModel.GetById 18ms app.highlight 3ms RepoModel.GetById 2ms app.codeStats 0ms

/AppKit/CPTableHeaderView.j

http://github.com/cacaodev/cappuccino
Unknown | 826 lines | 641 code | 185 blank | 0 comment | 0 complexity | 384eace7f204085f57fa124cbcd1c15d MD5 | raw 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@import "CPTableColumn.j"
 24@import "CPTableView.j"
 25@import "CPView.j"
 26@import "CPCursor.j"
 27@import "_CPImageAndTextView.j"
 28
 29@global CPApp
 30
 31@implementation _CPTableColumnHeaderView : CPView
 32{
 33    _CPImageAndTextView     _textField;
 34}
 35
 36+ (CPString)defaultThemeClass
 37{
 38    return @"columnHeader";
 39}
 40
 41+ (CPDictionary)themeAttributes
 42{
 43    return @{
 44            @"background-color": [CPNull null],
 45            @"text-alignment": CPLeftTextAlignment,
 46            @"line-break-mode": CPLineBreakByTruncatingTail,
 47            @"text-inset": CGInsetMakeZero(),
 48            @"text-color": [CPNull null],
 49            @"font": [CPNull null],
 50            @"text-shadow-color": [CPNull null],
 51            @"text-shadow-offset": CGSizeMakeZero()
 52        };
 53}
 54
 55- (id)initWithFrame:(CGRect)frame
 56{
 57    self = [super initWithFrame:frame];
 58
 59    if (self)
 60        [self _init];
 61
 62    return self;
 63}
 64
 65- (void)_init
 66{
 67    _textField = [[_CPImageAndTextView alloc] initWithFrame:
 68        CGRectMake(5.0, 0.0, CGRectGetWidth([self bounds]) - 10.0, CGRectGetHeight([self bounds]))];
 69
 70    [_textField setAutoresizingMask:CPViewWidthSizable|CPViewHeightSizable];
 71
 72    [_textField setLineBreakMode:CPLineBreakByTruncatingTail];
 73    [_textField setTextColor:[CPColor colorWithRed:51.0 / 255.0 green:51.0 / 255.0 blue:51.0 / 255.0 alpha:1.0]];
 74    [_textField setFont:[CPFont boldSystemFontOfSize:12.0]];
 75    [_textField setAlignment:CPLeftTextAlignment];
 76    [_textField setVerticalAlignment:CPCenterVerticalTextAlignment];
 77    [_textField setTextShadowColor:[CPColor whiteColor]];
 78    [_textField setTextShadowOffset:CGSizeMake(0,1)];
 79
 80    [self addSubview:_textField];
 81}
 82
 83- (void)layoutSubviews
 84{
 85    [self setBackgroundColor:[self currentValueForThemeAttribute:@"background-color"]];
 86
 87    var inset = [self currentValueForThemeAttribute:@"text-inset"],
 88        bounds = [self bounds];
 89
 90    [_textField setFrame:CGRectMake(inset.right, inset.top, bounds.size.width - inset.right - inset.left, bounds.size.height - inset.top - inset.bottom)];
 91    [_textField setTextColor:[self currentValueForThemeAttribute:@"text-color"]];
 92    [_textField setFont:[self currentValueForThemeAttribute:@"font"]];
 93    [_textField setTextShadowColor:[self currentValueForThemeAttribute:@"text-shadow-color"]];
 94    [_textField setTextShadowOffset:[self currentValueForThemeAttribute:@"text-shadow-offset"]];
 95    [_textField setAlignment:[self currentValueForThemeAttribute:@"text-alignment"]];
 96    [_textField setLineBreakMode:[self currentValueForThemeAttribute:@"line-break-mode"]];
 97}
 98
 99- (void)setStringValue:(CPString)string
100{
101    [_textField setText:string];
102}
103
104- (CPString)stringValue
105{
106    return [_textField text];
107}
108
109- (void)textField
110{
111    return _textField;
112}
113
114- (void)sizeToFit
115{
116    [_textField sizeToFit];
117}
118
119- (void)setFont:(CPFont)aFont
120{
121    [self setValue:aFont forThemeAttribute:@"font"];
122}
123
124- (CPFont)font
125{
126    return [self currentValueForThemeAttribute:@"font"]
127}
128
129- (void)setAlignment:(CPTextAlignment)alignment
130{
131    [self setValue:alignment forThemeAttribute:@"text-alignment"];
132}
133
134- (CPTextAlignment)alignment
135{
136    return [self currentValueForThemeAttribute:@"text-alignment"]
137}
138
139- (void)setLineBreakMode:(CPLineBreakMode)mode
140{
141    [self setValue:mode forThemeAttribute:@"line-break-mode"];
142}
143
144- (CPLineBreakMode)lineBreakMode
145{
146    return [self currentValueForThemeAttribute:@"line-break-mode"]
147}
148
149- (void)setTextColor:(CPColor)aColor
150{
151    [self setValue:aColor forThemeAttribute:@"text-color"];
152}
153
154- (CPColor)textColor
155{
156    return [self currentValueForThemeAttribute:@"text-color"]
157}
158
159- (void)setTextShadowColor:(CPColor)aColor
160{
161    [self setValue:aColor forThemeAttribute:@"text-shadow-color"];
162}
163
164- (CPColor)textShadowColor
165{
166    return [self currentValueForThemeAttribute:@"text-shadow-color"]
167}
168
169- (void)_setIndicatorImage:(CPImage)anImage
170{
171	if (anImage)
172	{
173		[_textField setImage:anImage];
174		[_textField setImagePosition:CPImageRight];
175	}
176	else
177	{
178		[_textField setImagePosition:CPNoImage];
179	}
180}
181
182- (CPImage)_indicatorImage
183{
184    return [_textField imagePosition] === CPNoImage ? nil : [_textField image];
185}
186
187- (void)drawRect:(CGRect)aRect
188{
189    var bounds = [self bounds];
190
191    if (!CGRectIntersectsRect(aRect, bounds))
192        return;
193
194    var context = [[CPGraphicsContext currentContext] graphicsPort],
195        maxX = CGRectGetMaxX(bounds) - 0.5;
196
197    CGContextSetLineWidth(context, 1);
198    CGContextSetStrokeColor(context, [CPColor colorWithWhite:192.0/255.0 alpha:1.0]);
199
200    CGContextBeginPath(context);
201
202    CGContextMoveToPoint(context, maxX, CGRectGetMinY(bounds));
203    CGContextAddLineToPoint(context, maxX, CGRectGetMaxY(bounds));
204
205    CGContextStrokePath(context);
206}
207
208@end
209
210var _CPTableColumnHeaderViewStringValueKey = @"_CPTableColumnHeaderViewStringValueKey",
211    _CPTableColumnHeaderViewFontKey = @"_CPTableColumnHeaderViewFontKey",
212    _CPTableColumnHeaderViewImageKey = @"_CPTableColumnHeaderViewImageKey";
213    _CPTableColumnHeaderViewIsDraggingKey = @"_CPTableColumnHeaderViewIsDraggingKey";
214
215@implementation _CPTableColumnHeaderView (CPCoding)
216
217- (id)initWithCoder:(CPCoder)aCoder
218{
219    if (self = [super initWithCoder:aCoder])
220    {
221        [self _init];
222        [self _setIndicatorImage:[aCoder decodeObjectForKey:_CPTableColumnHeaderViewImageKey]];
223        [self setStringValue:[aCoder decodeObjectForKey:_CPTableColumnHeaderViewStringValueKey]];
224        [self setFont:[aCoder decodeObjectForKey:_CPTableColumnHeaderViewFontKey]];
225        [self setFont:[aCoder decodeObjectForKey:_CPTableColumnHeaderViewFontKey]];
226    }
227
228    return self;
229}
230
231- (void)encodeWithCoder:(CPCoder)aCoder
232{
233    [super encodeWithCoder:aCoder];
234
235    [aCoder encodeObject:[_textField text] forKey:_CPTableColumnHeaderViewStringValueKey];
236    [aCoder encodeObject:[_textField image] forKey:_CPTableColumnHeaderViewImageKey];
237    [aCoder encodeObject:[_textField font] forKey:_CPTableColumnHeaderViewFontKey];
238}
239
240@end
241
242CPTableHeaderViewDragColumnHeaderTag = 1;
243
244var CPTableHeaderViewResizeZone = 3.0,
245    CPTableHeaderViewDragTolerance = 10.0;
246
247@implementation CPTableHeaderView : CPView
248{
249    CGPoint     _mouseDownLocation;
250    CGPoint     _columnMouseDownLocation;
251    CGPoint     _mouseEnterExitLocation;
252    CGPoint     _previousTrackingLocation;
253
254    int         _activeColumn;
255    int         _pressedColumn;
256
257    BOOL        _isResizing;
258    BOOL        _isDragging;
259    BOOL        _canDragColumn;
260
261    CPView      _columnDragView;
262    CPView      _columnDragHeaderView;
263    CPView      _columnDragClipView;
264
265    float       _columnOldWidth;
266
267    CPTableView _tableView @accessors(property=tableView);
268}
269
270+ (CPString)defaultThemeClass
271{
272    return @"tableHeaderRow";
273}
274
275+ (CPDictionary)themeAttributes
276{
277    return @{
278            @"background-color": [CPNull null],
279            @"divider-color": [CPColor grayColor],
280            @"divider-thickness": 1.0
281        };
282}
283
284- (void)_init
285{
286    _mouseDownLocation = CGPointMakeZero();
287    _columnMouseDownLocation = CGPointMakeZero();
288    _mouseEnterExitLocation = CGPointMakeZero();
289    _previousTrackingLocation = CGPointMakeZero();
290
291    _activeColumn = -1;
292    _pressedColumn = -1;
293
294    _isResizing = NO;
295    _isDragging = NO;
296    _canDragColumn = NO;
297
298    _columnOldWidth = 0.0;
299
300    [self setBackgroundColor:[self currentValueForThemeAttribute:@"background-color"]];
301}
302
303- (id)initWithFrame:(CGRect)aFrame
304{
305    self = [super initWithFrame:aFrame];
306
307    if (self)
308        [self _init];
309
310    return self;
311}
312
313// Checking Altered Columns
314
315- (CPInteger)draggedColumn
316{
317    return _isDragging ? _activeColumn : -1;
318}
319
320- (float)draggedDistance
321{
322    if (_isDragging)
323        return (CGRectGetMinX(_columnDragClipView) - _columnMouseDownLocation.x);
324    else
325        return -1;
326}
327
328- (CPInteger)resizedColumn
329{
330    if (_isResizing)
331        return _activeColumn;
332    else
333        return -1;
334}
335
336// Utility Methods
337
338- (CPInteger)columnAtPoint:(CGPoint)aPoint
339{
340    return [_tableView columnAtPoint:aPoint];
341}
342
343- (CGRect)headerRectOfColumn:(CPInteger)aColumnIndex
344{
345    var headerRect = [self bounds],
346        columnRect = [_tableView rectOfColumn:aColumnIndex];
347
348    headerRect.origin.x = CGRectGetMinX(columnRect);
349    headerRect.size.width = CGRectGetWidth(columnRect);
350
351    return headerRect;
352}
353
354// CPView Overrides
355
356- (void)viewDidMoveToWindow
357{
358    [super viewDidMoveToWindow];
359
360    [[self window] setAcceptsMouseMovedEvents:YES];
361}
362
363- (void)layoutSubviews
364{
365    var tableColumns = [_tableView tableColumns],
366        count = [tableColumns count];
367
368    for (var i = 0; i < count; i++)
369    {
370        var column = [tableColumns objectAtIndex:i],
371            headerView = [column headerView],
372            frame = [self headerRectOfColumn:i];
373
374        [headerView setFrame:frame];
375
376        if ([headerView superview] != self)
377            [self addSubview:headerView];
378    }
379}
380
381// CPResponder Overrides
382
383- (void)mouseDown:(CPEvent)theEvent
384{
385    var currentLocation = [self convertPoint:[theEvent locationInWindow] fromView:nil],
386        adjustedLocation = CGPointMake(MAX(currentLocation.x - CPTableHeaderViewResizeZone, 0.0), currentLocation.y),
387        columnIndex = [self columnAtPoint:adjustedLocation];
388
389    if (columnIndex === -1)
390        return;
391
392    _mouseDownLocation = currentLocation;
393    _activeColumn = columnIndex;
394    _canDragColumn = YES;
395
396        [_tableView _sendDelegateMouseDownInHeaderOfTableColumn:columnIndex];
397
398    if ([self _shouldResizeTableColumn:columnIndex at:currentLocation])
399        [self _startResizingTableColumn:columnIndex at:currentLocation];
400    else
401        [self _setPressedColumn:columnIndex];
402}
403
404- (void)mouseDragged:(CPEvent)theEvent
405{
406    var currentLocation = [self convertPoint:[theEvent locationInWindow] fromView:nil],
407        adjustedLocation = CGPointMake(MAX(currentLocation.x - CPTableHeaderViewResizeZone, 0.0), currentLocation.y),
408        columnIndex = [self columnAtPoint:adjustedLocation];
409
410    if (_isResizing)
411    {
412        [self _autoscroll:theEvent localLocation:currentLocation];
413        [self _continueResizingTableColumn:_activeColumn at:currentLocation];
414    }
415    else if (_isDragging)
416    {
417        // Disable autoscrolling until it behaves correctly.
418        //[self _autoscroll:theEvent localLocation:currentLocation];
419        [self _dragTableColumn:_activeColumn to:currentLocation];
420    }
421    else // tracking a press, could become a drag
422    {
423        if (CGRectContainsPoint([self headerRectOfColumn:_activeColumn], currentLocation))
424        {
425            if ([self _shouldDragTableColumn:columnIndex at:currentLocation])
426                [self _startDraggingTableColumn:columnIndex at:currentLocation];
427            else
428                [self _setPressedColumn:_activeColumn];
429        }
430        else
431            [self _setPressedColumn:-1];
432    }
433}
434
435- (void)mouseUp:(CPEvent)theEvent
436{
437    if (_isResizing)
438    {
439        [self _stopResizingTableColumn:_activeColumn];
440    }
441    else if (_isDragging)
442    {
443        [self _stopDraggingTableColumn:_activeColumn];
444    }
445    else if (_activeColumn != -1)
446    {
447        var currentLocation = [self convertPoint:[theEvent locationInWindow] fromView:nil];
448
449        if (CGRectContainsPoint([self headerRectOfColumn:_activeColumn], currentLocation))
450            [_tableView _didClickTableColumn:_activeColumn modifierFlags:[theEvent modifierFlags]];
451    }
452
453    [self _setPressedColumn:-1];
454    [self _updateResizeCursor:[CPApp currentEvent]];
455
456    _activeColumn = -1;
457}
458
459- (void)mouseEntered:(CPEvent)theEvent
460{
461    var location = [theEvent globalLocation];
462
463    if (CGPointEqualToPoint(location, _mouseEnterExitLocation))
464        return;
465
466    _mouseEnterExitLocation = location;
467
468    [self _updateResizeCursor:theEvent];
469}
470
471- (void)mouseMoved:(CPEvent)theEvent
472{
473    [self _updateResizeCursor:theEvent];
474}
475
476- (void)mouseExited:(CPEvent)theEvent
477{
478    var location = [theEvent globalLocation];
479
480    if (CGPointEqualToPoint(location, _mouseEnterExitLocation))
481        return;
482
483    _mouseEnterExitLocation = location;
484
485    // FIXME: we should use CPCursor push/pop (if previous currentCursor != arrow).
486    [[CPCursor arrowCursor] set];
487}
488
489@end
490
491@implementation CPTableHeaderView (CPTableHeaderViewPrivate)
492
493- (CGRect)_cursorRectForColumn:(CPInteger)column
494{
495    if (column == -1 || !([_tableView._tableColumns[column] resizingMask] & CPTableColumnUserResizingMask))
496        return CGRectMakeZero();
497
498    var rect = [self headerRectOfColumn:column];
499
500    rect.origin.x = (CGRectGetMaxX(rect) - CPTableHeaderViewResizeZone) - 1.0;
501    rect.size.width = (CPTableHeaderViewResizeZone * 2.0) + 1.0;  // + 1 for resize line
502
503    return rect;
504}
505
506- (void)_setPressedColumn:(CPInteger)column
507{
508    if (_pressedColumn === column)
509        return;
510
511    if (_pressedColumn != -1)
512    {
513        var headerView = [_tableView._tableColumns[_pressedColumn] headerView];
514        [headerView unsetThemeState:CPThemeStateHighlighted];
515    }
516
517    if (column != -1)
518    {
519        var headerView = [_tableView._tableColumns[column] headerView];
520        [headerView setThemeState:CPThemeStateHighlighted];
521    }
522    else
523    {
524        // Once the mouse leaves the pressed column, it can no longer drag
525        _canDragColumn = NO;
526    }
527
528    _pressedColumn = column;
529}
530
531- (BOOL)_shouldDragTableColumn:(CPInteger)aColumnIndex at:(CGPoint)aPoint
532{
533    return _canDragColumn && [_tableView allowsColumnReordering] && ABS(aPoint.x - _mouseDownLocation.x) >= CPTableHeaderViewDragTolerance;
534}
535
536- (void)_autoscroll:(CPEvent)theEvent localLocation:(CGPoint)theLocation
537{
538    // Constrain the y coordinate so we don't autoscroll vertically
539    var constrainedLocation = CGPointMake(theLocation.x, CGRectGetMinY([_tableView visibleRect])),
540        constrainedEvent = [CPEvent mouseEventWithType:CPLeftMouseDragged
541                                             location:[self convertPoint:constrainedLocation toView:nil]
542                                        modifierFlags:[theEvent modifierFlags]
543                                            timestamp:[theEvent timestamp]
544                                         windowNumber:[theEvent windowNumber]
545                                              context:nil
546                                          eventNumber:0
547                                           clickCount:[theEvent clickCount]
548                                             pressure:[theEvent pressure]];
549
550    [self autoscroll:constrainedEvent];
551    [_tableView autoscroll:constrainedEvent];
552}
553
554- (CGRect)_headerRectOfLastVisibleColumn
555{
556    var tableColumns = [_tableView tableColumns],
557        columnIndex = [tableColumns count];
558
559    while (columnIndex--)
560    {
561        var tableColumn = [tableColumns objectAtIndex:columnIndex];
562
563        if (![tableColumn isHidden])
564            return [self headerRectOfColumn:columnIndex];
565    }
566
567    return nil;
568}
569
570- (CGPoint)_constrainDragPoint:(CGPoint)aPoint
571{
572    // This effectively clamps the value between the minimum and maximum
573    var visibleRect = [_tableView visibleRect],
574        lastColumnRect = [self _headerRectOfLastVisibleColumn],
575        activeColumnRect = [self headerRectOfColumn:_activeColumn],
576        maxX = CGRectGetMaxX(lastColumnRect) - CGRectGetWidth(activeColumnRect) - CGRectGetMinX(visibleRect),
577        point = CGPointMake(MAX(MIN(aPoint.x, maxX), -CGRectGetMinX(visibleRect)), aPoint.y);
578
579    return point;
580}
581
582- (void)_moveColumn:(CPInteger)aFromIndex toColumn:(CPInteger)aToIndex
583{
584    [_tableView moveColumn:aFromIndex toColumn:aToIndex];
585    _activeColumn = aToIndex;
586    _pressedColumn = _activeColumn;
587
588    [_tableView _setDraggedColumn:_activeColumn];
589
590    [self setNeedsDisplay:YES];
591}
592
593- (BOOL)isDragging
594{
595    return _isDragging;
596}
597
598- (void)_startDraggingTableColumn:(CPInteger)aColumnIndex at:(CGPoint)aPoint
599{
600    _isDragging = YES;
601    _columnDragView = [_tableView _dragViewForColumn:aColumnIndex];
602    _previousTrackingLocation = aPoint;
603
604    // Create a new clip view for the drag view that clips to the header + visible content
605    var headerHeight = CGRectGetHeight([self frame]),
606        scrollView = [self enclosingScrollView],
607        contentFrame = [[scrollView contentView] frame];
608
609    contentFrame.origin.y -= headerHeight;
610    contentFrame.size.height += headerHeight;
611
612    _columnDragClipView = [[CPView alloc] initWithFrame:contentFrame];
613
614    [_columnDragClipView addSubview:_columnDragView];
615
616    // Insert the clip view above the table header (and content)
617    [scrollView addSubview:_columnDragClipView positioned:CPWindowAbove relativeTo:self];
618
619    // Hide the underlying column header subviews, we just want to draw the chrome
620    var headerView = [[[_tableView tableColumns] objectAtIndex:aColumnIndex] headerView];
621
622    [[headerView subviews] makeObjectsPerformSelector:@selector(setHidden:) withObject:YES];
623
624    // The underlying column header shows normal state
625    [headerView unsetThemeState:CPThemeStateHighlighted | CPThemeStateSelected];
626
627    // Keep track of the location within the column header where the original mousedown occurred
628    _columnDragHeaderView = [_columnDragView viewWithTag:CPTableHeaderViewDragColumnHeaderTag];
629
630    _columnMouseDownLocation = [self convertPoint:_mouseDownLocation toView:_columnDragHeaderView];
631
632    [_tableView _setDraggedColumn:aColumnIndex];
633
634    [[CPCursor closedHandCursor] set];
635
636    [self setNeedsDisplay:YES];
637}
638
639- (void)_dragTableColumn:(CPInteger)aColumnIndex to:(CGPoint)aPoint
640{
641    var delta = aPoint.x - _previousTrackingLocation.x,
642        columnPoint = [_columnDragHeaderView convertPoint:aPoint fromView:self];
643
644    // Only move if the mouse is past the original click point in the direction of movement
645    if ((delta > 0 && columnPoint.x > _columnMouseDownLocation.x) || (delta < 0 && columnPoint.x < _columnMouseDownLocation.x))
646    {
647        var dragFrame = [_columnDragView frame],
648            newOrigin = [self _constrainDragPoint:CGPointMake(CGRectGetMinX(dragFrame) + delta, CGRectGetMinY(dragFrame))];
649
650        [_columnDragView setFrameOrigin:newOrigin];
651
652        // When the edge of the dragged column passes the midpoint of an adjacent column, they swap
653        var hoverPoint = CGPointMakeCopy(aPoint);
654
655        // The drag frame is in content view coordinates, we need it to be in our coordinates
656        dragFrame = [self convertRect:dragFrame fromView:[_columnDragView superview]];
657
658        if (delta > 0)
659            hoverPoint.x = CGRectGetMaxX(dragFrame);
660        else
661            hoverPoint.x = CGRectGetMinX(dragFrame);
662
663        var hoveredColumn = [self columnAtPoint:hoverPoint];
664
665        if (hoveredColumn !== -1)
666        {
667            var columnRect = [self headerRectOfColumn:hoveredColumn],
668                columnCenterPoint = CGPointMake(CGRectGetMidX(columnRect), CGRectGetMidY(columnRect));
669
670            if (hoveredColumn < _activeColumn && hoverPoint.x < columnCenterPoint.x)
671                [self _moveColumn:_activeColumn toColumn:hoveredColumn];
672            else if (hoveredColumn > _activeColumn && hoverPoint.x > columnCenterPoint.x)
673                [self _moveColumn:_activeColumn toColumn:hoveredColumn];
674        }
675    }
676
677    _previousTrackingLocation = aPoint;
678}
679
680- (void)_stopDraggingTableColumn:(CPInteger)aColumnIndex
681{
682    _isDragging = NO;
683
684    [_columnDragClipView removeFromSuperview];
685    [_tableView _setDraggedColumn:-1];
686
687    var headerView = [[[_tableView tableColumns] objectAtIndex:aColumnIndex] headerView];
688
689    [[headerView subviews] makeObjectsPerformSelector:@selector(setHidden:) withObject:NO];
690
691    if (_tableView._draggedColumnIsSelected)
692        [headerView setThemeState:CPThemeStateSelected];
693
694    var columnRect = [_tableView rectOfColumn:aColumnIndex];
695
696    [_tableView _reloadDataViews];
697    [[_tableView headerView] setNeedsLayout];
698
699    [[CPCursor arrowCursor] set];
700}
701
702- (BOOL)_shouldResizeTableColumn:(CPInteger)aColumnIndex at:(CGPoint)aPoint
703{
704    if (_isResizing)
705        return YES;
706
707    return [_tableView allowsColumnResizing] && CGRectContainsPoint([self _cursorRectForColumn:aColumnIndex], aPoint);
708}
709
710- (void)_startResizingTableColumn:(CPInteger)aColumnIndex at:(CGPoint)aPoint
711{
712    _isResizing = YES;
713    _previousTrackingLocation = aPoint;
714    _activeColumn = aColumnIndex;
715
716    var tableColumn = [[_tableView tableColumns] objectAtIndex:aColumnIndex];
717
718    _columnOldWidth = [tableColumn width];
719
720    [tableColumn setDisableResizingPosting:YES];
721    [_tableView setDisableAutomaticResizing:YES];
722}
723
724- (void)_continueResizingTableColumn:(CPInteger)aColumnIndex at:(CGPoint)aPoint
725{
726    var tableColumn = [[_tableView tableColumns] objectAtIndex:aColumnIndex],
727        delta = aPoint.x - _previousTrackingLocation.x,
728        spacing = [_tableView intercellSpacing].width,
729        newWidth = [tableColumn width] + spacing + delta,
730        minWidth = [tableColumn minWidth] + spacing,
731        maxWidth = [tableColumn maxWidth] + spacing;
732
733    if (newWidth <= minWidth)
734        [[CPCursor resizeRightCursor] set];
735    else if (newWidth >= maxWidth)
736        [[CPCursor resizeLeftCursor] set];
737    else
738        [[CPCursor resizeLeftRightCursor] set];
739
740    var columnRect = [_tableView rectOfColumn:aColumnIndex],
741        columnWidth = CGRectGetWidth(columnRect);
742
743    if ((delta > 0 && columnWidth == maxWidth) || (delta < 0 && columnWidth == minWidth))
744        return;
745
746    var columnMinX = CGRectGetMinX(columnRect),
747        columnMaxX = CGRectGetMaxX(columnRect);
748
749    if ((delta > 0 && aPoint.x > columnMaxX) || (delta < 0 && aPoint.x < columnMaxX))
750    {
751        [tableColumn setWidth:newWidth - spacing];
752
753        [self setNeedsLayout];
754        [self setNeedsDisplay:YES];
755    }
756
757    _previousTrackingLocation = aPoint;
758}
759
760- (void)_stopResizingTableColumn:(CPInteger)aColumnIndex
761{
762    var tableColumn = [[_tableView tableColumns] objectAtIndex:aColumnIndex];
763
764    if ([tableColumn width] != _columnOldWidth)
765        [_tableView _didResizeTableColumn:tableColumn oldWidth:_columnOldWidth];
766
767    [tableColumn setDisableResizingPosting:NO];
768    [_tableView setDisableAutomaticResizing:NO];
769
770    _isResizing = NO;
771}
772
773- (void)_updateResizeCursor:(CPEvent)theEvent
774{
775    // never get stuck in resize cursor mode (FIXME take out when we turn on tracking rects)
776    if (![_tableView allowsColumnResizing] || ([theEvent type] === CPLeftMouseUp && ![[self window] acceptsMouseMovedEvents]))
777    {
778        [[CPCursor arrowCursor] set];
779        return;
780    }
781
782    var mouseLocation = [self convertPoint:[theEvent locationInWindow] fromView:nil],
783        mouseOverLocation = CGPointMake(MAX(mouseLocation.x - CPTableHeaderViewResizeZone, 0.0), mouseLocation.y),
784        overColumn = [self columnAtPoint:mouseOverLocation];
785
786    if (overColumn >= 0 && CGRectContainsPoint([self _cursorRectForColumn:overColumn], mouseLocation))
787    {
788        var tableColumn = [[_tableView tableColumns] objectAtIndex:overColumn],
789            spacing = [_tableView intercellSpacing].width,
790            width = [tableColumn width];
791
792        if (width <= [tableColumn minWidth])
793            [[CPCursor resizeRightCursor] set];
794        else if (width >= [tableColumn maxWidth])
795            [[CPCursor resizeLeftCursor] set];
796        else
797            [[CPCursor resizeLeftRightCursor] set];
798    }
799    else
800        [[CPCursor arrowCursor] set];
801}
802
803@end // CPTableView (CPTableViewPrivate)
804
805var CPTableHeaderViewTableViewKey = @"CPTableHeaderViewTableViewKey";
806
807@implementation CPTableHeaderView (CPCoding)
808
809- (id)initWithCoder:(CPCoder)aCoder
810{
811    if (self = [super initWithCoder:aCoder])
812    {
813        [self _init];
814        _tableView = [aCoder decodeObjectForKey:CPTableHeaderViewTableViewKey];
815    }
816
817    return self;
818}
819
820- (void)encodeWithCoder:(CPCoder)aCoder
821{
822    [super encodeWithCoder:aCoder];
823    [aCoder encodeObject:_tableView forKey:CPTableHeaderViewTableViewKey];
824}
825
826@end