~ubuntu-branches/ubuntu/wily/oolite/wily-proposed

« back to all changes in this revision

Viewing changes to DebugOXP/Source/RBSplitView-114/RBSplitSubview.m

  • Committer: Package Import Robot
  • Author(s): Nicolas Boulenguez
  • Date: 2011-12-22 00:22:39 UTC
  • mfrom: (1.2.2)
  • Revision ID: package-import@ubuntu.com-20111222002239-pr3upeupp4jw1psp
Tags: 1.76-1
* New upstream.
* watch: scan upstream stable releases instead of dev snapshots.
* control: use default gobjc instead of explicit 4.6.
* rules: use dpkg-dev build flags.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
//
 
2
//  RBSplitSubview.m version 1.1.4
 
3
//  RBSplitView
 
4
//
 
5
//  Created by Rainer Brockerhoff on 19/11/2004.
 
6
//  Copyright 2004-2006 Rainer Brockerhoff.
 
7
//      Some Rights Reserved under the Creative Commons Attribution License, version 2.5, and/or the MIT License.
 
8
//
 
9
 
 
10
#import "RBSplitView.h"
 
11
#import "RBSplitViewPrivateDefines.h"
 
12
 
 
13
// This variable points to the animation data structure while an animation is in
 
14
// progress; if there's none, it will be NULL. Animating may be very CPU-intensive so
 
15
// we allow only one animation to take place at a time.
 
16
static animationData* currentAnimation = NULL;
 
17
 
 
18
@implementation RBSplitSubview
 
19
 
 
20
// This class method returns YES if an animation is in progress.
 
21
+ (BOOL)animating {
 
22
        return currentAnimation!=NULL;
 
23
}
 
24
 
 
25
// This is the designated initializer for RBSplitSubview. It sets some reasonable defaults. However, you
 
26
// can't rely on anything working until you insert it into a RBSplitView.
 
27
- (id)initWithFrame:(NSRect)frame {
 
28
    self = [super initWithFrame:frame];
 
29
        if (self) {
 
30
                fraction = 0.0;
 
31
                canCollapse = NO;
 
32
                notInLimits = NO;
 
33
                minDimension = 1.0;
 
34
                maxDimension = WAYOUT;
 
35
                identifier = @"";
 
36
                previous = NSZeroRect;
 
37
                savedSize = frame.size;
 
38
                actDivider = NSNotFound;
 
39
                canDragWindow = NO;
 
40
        }
 
41
        return self;
 
42
}
 
43
 
 
44
// Just releases our stuff when going away.
 
45
- (void)dealloc {
 
46
        [identifier release];
 
47
        [super dealloc];
 
48
}
 
49
 
 
50
// These return nil since we're not a RBSplitView (they're overridden there).
 
51
- (RBSplitView*)asSplitView {
 
52
        return nil;
 
53
}
 
54
 
 
55
- (RBSplitView*)coupledSplitView {
 
56
        return nil;
 
57
}
 
58
 
 
59
// Sets and gets the coupling between a RBSplitView and its containing RBSplitView (if any).
 
60
// For convenience, these methods are also implemented here.
 
61
- (void)setCoupled:(BOOL)flag {
 
62
}
 
63
 
 
64
- (BOOL)isCoupled {
 
65
        return NO;
 
66
}
 
67
 
 
68
// RBSplitSubviews are never flipped, unless they're RBSplitViews.
 
69
- (BOOL)isFlipped {
 
70
        return NO;
 
71
}
 
72
 
 
73
// We copy the opacity of the owning split view.
 
74
- (BOOL)isOpaque {
 
75
        return [[self couplingSplitView] isOpaque];
 
76
}
 
77
 
 
78
// A hidden RBSplitSubview is not redrawn and is not considered for drawing dividers.
 
79
// This won't work before 10.3, though.
 
80
- (void)setHidden:(BOOL)flag {
 
81
        if ([self isHidden]!=flag) {
 
82
                RBSplitView* sv = [self splitView];
 
83
                [self RB___setHidden:flag];
 
84
                if (flag) {
 
85
                        [sv adjustSubviews];
 
86
                } else {
 
87
                        [sv adjustSubviewsExcepting:self];
 
88
                }
 
89
        }
 
90
}
 
91
 
 
92
// RBSplitSubviews can't be in the responder chain.
 
93
- (BOOL)acceptsFirstResponder {
 
94
        return NO;
 
95
}
 
96
 
 
97
// Mousing down should move the window only for a completely transparent background. This might have
 
98
// unintended side effects in metal windows, so for those you might want to use a background color
 
99
// with a very low alpha (0.01 for instance).
 
100
// This is commented out as I'm still experimenting with it.
 
101
/*- (BOOL)mouseDownCanMoveWindow {
 
102
        RBSplitView* sv = [self asSplitView];
 
103
        if (!sv) {
 
104
                sv = [self couplingSplitView];
 
105
        }
 
106
        return [sv background]==nil;
 
107
        return YES;
 
108
}*/
 
109
 
 
110
// This returns the owning splitview. It's guaranteed to return a RBSplitView or nil.
 
111
// You should avoid having "orphan" RBSplitSubviews, or at least manipulating
 
112
// them while they're not inserted in a RBSplitView.
 
113
- (RBSplitView*)splitView {
 
114
        id result = [self superview];
 
115
        if ([result isKindOfClass:[RBSplitView class]]) {
 
116
                return (RBSplitView*)result;
 
117
        }
 
118
        return nil;
 
119
}
 
120
 
 
121
// This also returns the owning splitview. It's overridden for nested RBSplitViews.
 
122
- (RBSplitView*)couplingSplitView {
 
123
        id result = [self superview];
 
124
        if ([result isKindOfClass:[RBSplitView class]]) {
 
125
                return (RBSplitView*)result;
 
126
        }
 
127
        return nil;
 
128
}
 
129
 
 
130
// This returns the outermost directly containing RBSplitView, or nil.
 
131
- (RBSplitView*)outermostSplitView {
 
132
        id result = nil;
 
133
        id sv = self;
 
134
        while ((sv = [sv superview])&&[sv isKindOfClass:[RBSplitView class]]) {
 
135
                result = sv;
 
136
        }
 
137
        return result;
 
138
}
 
139
 
 
140
// This convenience method returns YES if the containing RBSplitView is horizontal.
 
141
- (BOOL)splitViewIsHorizontal {
 
142
        return [[self splitView] isHorizontal];
 
143
}
 
144
 
 
145
// You can use either tags (ints) or identifiers (NSStrings) to identify individual subviews.
 
146
// We take care not to have nil identifiers.
 
147
- (void)setTag:(OOInteger)theTag {
 
148
        tag = theTag;
 
149
}
 
150
 
 
151
- (OOInteger)tag {
 
152
        return tag;
 
153
}
 
154
 
 
155
- (void)setIdentifier:(NSString*)aString {
 
156
        [identifier autorelease];
 
157
        identifier = aString?[aString retain]:@"";
 
158
}
 
159
 
 
160
- (NSString*)identifier {
 
161
        return identifier;
 
162
}
 
163
 
 
164
// If we have an identifier, this will make debugging a little easier by appending it to the
 
165
// default description.
 
166
- (NSString*)description {
 
167
        return [identifier length]>0?[NSString stringWithFormat:@"%@(%@)",[super description],identifier]:[super description];
 
168
}
 
169
 
 
170
// This pair of methods allows you to get and change the position of a subview (within the split view);
 
171
// this counts from zero from the left or top of the split view.
 
172
- (unsigned)position {
 
173
        RBSplitView* sv = [self splitView];
 
174
        return sv?[[sv subviews] indexOfObjectIdenticalTo:self]:0;
 
175
}
 
176
 
 
177
- (void)setPosition:(unsigned)newPosition {
 
178
        RBSplitView* sv = [self splitView];
 
179
        if (sv) {
 
180
                [self retain];
 
181
                [self removeFromSuperviewWithoutNeedingDisplay];
 
182
                NSArray* subviews = [sv subviews];
 
183
                if (newPosition>=[subviews count]) {
 
184
                        [sv addSubview:self positioned:NSWindowAbove relativeTo:nil];
 
185
                } else {
 
186
                        [sv addSubview:self positioned:NSWindowBelow relativeTo:[subviews objectAtIndex:newPosition]];
 
187
                }
 
188
                [self release];
 
189
        }
 
190
}
 
191
 
 
192
// Tests whether the subview is collapsed.
 
193
- (BOOL)isCollapsed {
 
194
        return [self RB___visibleDimension]<=0.0;
 
195
}
 
196
 
 
197
// Tests whether the subview can shrink further.
 
198
- (BOOL)canShrink {
 
199
        return [self RB___visibleDimension]>([self canCollapse]?0.0:minDimension);
 
200
}
 
201
 
 
202
// Tests whether the subview can expand further.
 
203
- (BOOL)canExpand {
 
204
        return [self RB___visibleDimension]<maxDimension;
 
205
}
 
206
 
 
207
// Returns the subview's status.
 
208
- (RBSSubviewStatus)status {
 
209
        animationData* anim = [self RB___animationData:NO resize:NO];
 
210
        if (anim) {
 
211
                return anim->collapsing?RBSSubviewCollapsing:RBSSubviewExpanding;
 
212
        }
 
213
        return [self RB___visibleDimension]<=0.0?RBSSubviewCollapsed:RBSSubviewNormal;
 
214
}
 
215
 
 
216
// Tests whether the subview can be collapsed. The local instance variable will be overridden by the
 
217
// delegate method if it's implemented.
 
218
- (BOOL)canCollapse {
 
219
        BOOL result = canCollapse;
 
220
        RBSplitView* sv = [self splitView];
 
221
        if ([sv RB___numberOfSubviews]<2) {
 
222
                return NO;
 
223
        }
 
224
        id delegate = [sv delegate];
 
225
        if ([delegate respondsToSelector:@selector(splitView:canCollapse:)]) {
 
226
                result = [delegate splitView:sv canCollapse:self];
 
227
        }
 
228
        return result;
 
229
}
 
230
 
 
231
// This sets the subview's "canCollapse" flag. Ignored if the delegate's splitView:canCollapse:
 
232
// method is implemented.
 
233
- (void)setCanCollapse:(BOOL)flag {
 
234
        canCollapse = flag;
 
235
}
 
236
 
 
237
// This expands a collapsed subview and calls the delegate's splitView:didExpand: method, if it exists.
 
238
// This is not called internally by other methods; call this to expand a subview programmatically.
 
239
// As a convenience to other methods, it returns the subview's dimension after expanding (this may be
 
240
// off by 1 pixel due to rounding) or 0.0 if it couldn't be expanded.
 
241
// The delegate should not change the subview's frame.
 
242
- (float)expand {
 
243
        return [self RB___expandAndSetToMinimum:NO];
 
244
}
 
245
 
 
246
// This collapses an expanded subview and calls the delegate's splitView:didCollapse: method, if it exists.
 
247
// This is not called internally by other methods; call this to expand a subview programmatically.
 
248
// As a convenience to other methods, it returns the negative of the subview's dimension before
 
249
// collapsing (or 0.0 if it couldn't be collapsed).
 
250
// The delegate should not change the subview's frame.
 
251
- (float)collapse {
 
252
        return [self RB___collapse];
 
253
}
 
254
 
 
255
// This tries to collapse the subview with animation, and collapses it instantly if some other
 
256
// subview is animating. Returns YES if animation was started successfully.
 
257
- (BOOL)collapseWithAnimation {
 
258
        return [self collapseWithAnimation:YES withResize:YES];
 
259
}
 
260
 
 
261
// This tries to expand the subview with animation, and expands it instantly if some other
 
262
// subview is animating. Returns YES if animation was started successfully.
 
263
- (BOOL)expandWithAnimation {
 
264
        return [self expandWithAnimation:YES withResize:YES];
 
265
}
 
266
 
 
267
// These methods collapse and expand subviews with animation, depending on the parameters.
 
268
// They return YES if animation startup was successful. If resize is NO, the subview is
 
269
// collapsed/expanded without resizing it during animation.
 
270
- (BOOL)collapseWithAnimation:(BOOL)animate withResize:(BOOL)resize {
 
271
        if ([self status]==RBSSubviewNormal) {
 
272
                if ([self canCollapse]) {
 
273
                        if (animate&&[self RB___animationData:YES resize:resize]) {
 
274
                                [self RB___clearResponder];
 
275
                                [self RB___stepAnimation];
 
276
                                return YES;
 
277
                        } else {
 
278
                                [self RB___collapse];
 
279
                        }
 
280
                }
 
281
        }
 
282
        return NO;
 
283
}
 
284
 
 
285
- (BOOL)expandWithAnimation:(BOOL)animate withResize:(BOOL)resize {
 
286
        if ([self status]==RBSSubviewCollapsed) {
 
287
                if (animate&&[self RB___animationData:YES resize:resize]) {
 
288
                        [self RB___stepAnimation];
 
289
                        return YES;
 
290
                } else {
 
291
                        [self RB___expandAndSetToMinimum:NO];
 
292
                }
 
293
        }
 
294
        return NO;
 
295
}
 
296
 
 
297
 
 
298
// These 3 methods get and set the view's minimum and maximum dimensions.
 
299
// The minimum dimension ought to be an integer at least equal to 1.0 but we make sure.
 
300
// The maximum dimension ought to be an integer at least equal to the minimum. As a convenience,
 
301
// pass in zero to set it to some huge number.
 
302
- (float)minDimension {
 
303
        return minDimension;
 
304
}
 
305
 
 
306
- (float)maxDimension {
 
307
        return maxDimension;
 
308
}
 
309
 
 
310
- (void)setMinDimension:(float)newMinDimension andMaxDimension:(float)newMaxDimension {
 
311
        minDimension = MAX(1.0,floorf(newMinDimension));
 
312
        if (newMaxDimension<1.0) {
 
313
                newMaxDimension = WAYOUT;
 
314
        }
 
315
        maxDimension = MAX(minDimension,floorf(newMaxDimension));
 
316
        float dim = [self dimension];
 
317
        if ((dim<minDimension)||(dim>maxDimension)) {
 
318
                [[self splitView] setMustAdjust];
 
319
        }
 
320
}
 
321
 
 
322
// This returns the subview's dimension. If it's collapsed, it returns the dimension it would have
 
323
// after expanding.
 
324
- (float)dimension {
 
325
        float dim = [self RB___visibleDimension];
 
326
        if (dim<=0.0) {
 
327
                dim = [[self splitView] RB___dimensionWithoutDividers]*fraction;
 
328
                if (dim<minDimension) {
 
329
                        dim = minDimension;
 
330
                } else if (dim>maxDimension) {
 
331
                        dim = maxDimension;
 
332
                }
 
333
        }
 
334
        return dim;
 
335
}
 
336
 
 
337
// Sets the current dimension of the subview, subject to the current maximum and minimum.
 
338
// If the subview is collapsed, this will have an effect only after reexpanding.
 
339
- (void)setDimension:(float)value {
 
340
        RBSplitView* sv = [self splitView];
 
341
        NSSize size = [self frame].size;
 
342
        BOOL ishor = [sv isHorizontal];
 
343
        if (DIM(size)>0.0) {
 
344
// We're not collapsed, set the size and adjust other subviews.
 
345
                DIM(size) = value;
 
346
                [self setFrameSize:size];
 
347
                [sv adjustSubviewsExcepting:self];
 
348
        } else {
 
349
// We're collapsed, adjust the fraction so that we'll have the (approximately) correct
 
350
// dimension after expanding.
 
351
                fraction = value/[sv RB___dimensionWithoutDividers];
 
352
        }
 
353
}
 
354
 
 
355
// This just draws the background of a subview, then tells the delegate, if any.
 
356
// The delegate would usually draw a frame inside the subview.
 
357
- (void)drawRect:(NSRect)rect {
 
358
        RBSplitView* sv = [self splitView];
 
359
        NSColor* bg = [sv background];
 
360
        if (bg) {
 
361
                [bg set];
 
362
                NSRectFillUsingOperation(rect,NSCompositeSourceOver);
 
363
        }
 
364
        id del = [sv delegate];
 
365
        if ([del respondsToSelector:@selector(splitView:willDrawSubview:inRect:)]) {
 
366
                [del splitView:sv willDrawSubview:self inRect:rect];
 
367
        }
 
368
}
 
369
 
 
370
// We check if the RBSplitView must be adjusted before redisplaying programmatically.
 
371
// if so, we adjust and display the whole RBSplitView.
 
372
- (void)display {
 
373
        RBSplitView* sv = [self splitView];
 
374
        if (sv) {
 
375
                if ([sv mustAdjust]) {
 
376
                        [sv display];
 
377
                } else {
 
378
                        [super display];
 
379
                }
 
380
        }
 
381
}
 
382
 
 
383
// RBSplitSubviews will always resize their own subviews.
 
384
- (BOOL)autoresizesSubviews {
 
385
        return YES;
 
386
}
 
387
 
 
388
// This is method is called automatically when the subview is resized; don't call it yourself.
 
389
- (void)resizeSubviewsWithOldSize:(NSSize)oldBoundsSize {
 
390
        RBSplitView* sv = [self splitView];
 
391
        if (sv) {
 
392
                BOOL ishor = [sv isHorizontal];
 
393
                NSRect frame = [self frame];
 
394
                float dim = DIM(frame.size);
 
395
                float other = OTHER(frame.size);
 
396
// We resize subviews only when we're inside the subview's limits and the containing splitview's limits.
 
397
                animationData* anim = [self RB___animationData:NO resize:NO];
 
398
                if ((dim>=(anim&&!anim->resizing?anim->dimension:minDimension))&&(dim<=maxDimension)&&(other>=[sv minDimension])&&(other<=[sv maxDimension])) {
 
399
                        if (notInLimits) {
 
400
// The subviews can be resized, so we restore the saved size.
 
401
                                oldBoundsSize = savedSize;
 
402
                        }
 
403
// We save the size every time the subview's subviews are resized within the limits.
 
404
                        notInLimits = NO;
 
405
                        savedSize = frame.size;
 
406
                        [super resizeSubviewsWithOldSize:oldBoundsSize];
 
407
                } else {
 
408
                        notInLimits = YES;
 
409
                }
 
410
        }
 
411
}
 
412
 
 
413
// This method is used internally when a divider is dragged. It tries to change the subview's dimension
 
414
// and returns the actual change, collapsing or expanding whenever possible. You usually won't need
 
415
// to call this directly.
 
416
- (float)changeDimensionBy:(float)increment mayCollapse:(BOOL)mayCollapse move:(BOOL)move {
 
417
        RBSplitView* sv = [self splitView];
 
418
        if (!sv||(fabsf(increment)<1.0)) {
 
419
                return 0.0;
 
420
        }
 
421
        BOOL ishor = [sv isHorizontal];
 
422
        NSRect frame = [self frame];
 
423
        float olddim = DIM(frame.size);
 
424
        float newdim = MAX(0.0,olddim+increment);
 
425
        if (newdim<olddim) {
 
426
                if (newdim<minDimension) {
 
427
// Collapse if needed
 
428
                        if (mayCollapse&&[self canCollapse]&&(newdim<MAX(1.0,minDimension*(0.5-HYSTERESIS)))) {
 
429
                                return [self RB___collapse];
 
430
                        }
 
431
                        newdim = minDimension;
 
432
                }
 
433
        } else if (newdim>olddim) {
 
434
                if (olddim<1.0) {
 
435
// Expand if needed.
 
436
                        if (newdim>(minDimension*(0.5+HYSTERESIS))) {
 
437
                                newdim = MAX(newdim,[self RB___expandAndSetToMinimum:YES]);
 
438
                        } else {
 
439
                                return 0.0;
 
440
                        }
 
441
                }
 
442
                if (newdim>maxDimension) {
 
443
                        newdim = maxDimension;
 
444
                }
 
445
        }
 
446
        if ((int)newdim!=(int)olddim) {
 
447
// The dimension has changed.
 
448
                increment = newdim-olddim;
 
449
                DIM(frame.size) = newdim;
 
450
                if (move) {
 
451
                        DIM(frame.origin) -= increment;
 
452
                }
 
453
// We call super instead of self here to postpone adjusting subviews for nested splitviews.
 
454
//              [super setFrameSize:frame.size];
 
455
                [super setFrame:frame];
 
456
                [sv RB___setMustClearFractions];
 
457
                [sv setMustAdjust];
 
458
        }
 
459
        return newdim-olddim;
 
460
}
 
461
 
 
462
// This convenience method returns the number of subviews (surprise!)
 
463
- (unsigned)numberOfSubviews {
 
464
        return [[self subviews] count];
 
465
}
 
466
 
 
467
// We return the deepest subview that's hit by aPoint. We also check with the delegate if aPoint is
 
468
// within an alternate drag view.
 
469
- (NSView*)hitTest:(NSPoint)aPoint {
 
470
        RBSplitView* sv = [self splitView];
 
471
        if ([self mouse:aPoint inRect:[self frame]]) {
 
472
                id delegate = [sv delegate];
 
473
                if ([delegate respondsToSelector:@selector(splitView:dividerForPoint:inSubview:)]) {
 
474
                        actDivider = [delegate splitView:sv dividerForPoint:aPoint inSubview:self];
 
475
                        if ((int)actDivider<(int)([sv RB___numberOfSubviews]-1)) {
 
476
                                return self;
 
477
                        }
 
478
                }
 
479
                actDivider = NSNotFound;
 
480
                NSView* result = [super hitTest:aPoint];
 
481
                canDragWindow = ![result isOpaque];
 
482
                return result;
 
483
        }
 
484
        return nil;
 
485
}
 
486
 
 
487
// This method handles clicking and dragging in an empty portion of the subview, or in an alternate
 
488
// drag view as designated by the delegate.
 
489
- (void)mouseDown:(NSEvent*)theEvent {
 
490
        NSWindow* window = [self window];
 
491
        NSPoint where = [theEvent locationInWindow];
 
492
        if (actDivider<NSNotFound) {
 
493
// The mouse down was inside an alternate drag view; actDivider was just set in hitTest.
 
494
                RBSplitView* sv = [self splitView];
 
495
                NSPoint point = [sv convertPoint:where fromView:nil];
 
496
                [[RBSplitView cursor:RBSVDragCursor] push];
 
497
                NSPoint base = NSZeroPoint;
 
498
// Record the current divider coordinate.
 
499
                float divc = [sv RB___dividerOrigin:actDivider];
 
500
                BOOL ishor = [sv isHorizontal];
 
501
                [sv RB___setDragging:YES];
 
502
// Loop while the button is down.
 
503
                while ((theEvent = [NSApp nextEventMatchingMask:NSLeftMouseDownMask|NSLeftMouseDraggedMask|NSLeftMouseUpMask untilDate:[NSDate distantFuture] inMode:NSEventTrackingRunLoopMode dequeue:YES])&&([theEvent type]!=NSLeftMouseUp)) {
 
504
// Set up a local autorelease pool for the loop to prevent buildup of temporary objects.
 
505
                        NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
 
506
                        NSDisableScreenUpdates();
 
507
// This does the actual movement.
 
508
                        [sv RB___trackMouseEvent:theEvent from:point withBase:base inDivider:actDivider];
 
509
                        if ([sv mustAdjust]) {
 
510
// If something changed, we clear fractions and redisplay.
 
511
                                [sv RB___setMustClearFractions];
 
512
                                [sv display];
 
513
                        }
 
514
// Change the drag point by the actual amount moved.
 
515
                        float newc = [sv RB___dividerOrigin:actDivider];
 
516
                        DIM(point) += newc-divc;
 
517
                        divc = newc;
 
518
                        NSEnableScreenUpdates();
 
519
                        [pool release];
 
520
                }
 
521
                [sv RB___setDragging:NO];
 
522
                [NSCursor pop];
 
523
                actDivider = NSNotFound;
 
524
                return;
 
525
        }
 
526
        if (canDragWindow&&[window isMovableByWindowBackground]&&![[self couplingSplitView] background]) {
 
527
// If we get here, it's a textured (metal) window, the mouse has gone down on an non-opaque portion
 
528
// of the subview, and our RBSplitView has a transparent background. RBSplitView returns NO to
 
529
// mouseDownCanMoveWindow, but the window should move here - after all, the window background
 
530
// is visible right here! So we fake it and move the window as intended. Mwahahaha!
 
531
                where =  [window convertBaseToScreen:where];
 
532
                NSPoint origin = [window frame].origin;
 
533
// Now we loop handling mouse events until we get a mouse up event.
 
534
                while ((theEvent = [NSApp nextEventMatchingMask:NSLeftMouseDownMask|NSLeftMouseDraggedMask|NSLeftMouseUpMask untilDate:[NSDate distantFuture] inMode:NSEventTrackingRunLoopMode dequeue:YES])&&([theEvent type]!=NSLeftMouseUp)) {
 
535
// Set up a local autorelease pool for the loop to prevent buildup of temporary objects.
 
536
                        NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
 
537
                        NSPoint now = [window convertBaseToScreen:[theEvent locationInWindow]];
 
538
                        origin.x += now.x-where.x;
 
539
                        origin.y += now.y-where.y;
 
540
// Move the window by the mouse displacement since the last event.
 
541
                        [window setFrameOrigin:origin];
 
542
                        where = now;
 
543
                        [pool release];
 
544
                }
 
545
        }
 
546
}
 
547
 
 
548
// These two methods encode and decode subviews.
 
549
- (void)encodeWithCoder:(NSCoder*)coder {
 
550
        NSRect frame;
 
551
        BOOL coll = [self isCollapsed];
 
552
        if (coll) {
 
553
// We can't encode a collapsed subview as-is, so we correct the frame size first and add WAYOUT
 
554
// to the origin to signal it was collapsed. 
 
555
                NSRect newf = frame = [self frame];
 
556
                newf.origin.x += WAYOUT;
 
557
                [super setFrameOrigin:newf.origin];
 
558
                newf.size = savedSize;
 
559
                [super setFrameSize:newf.size];
 
560
        }
 
561
        [super encodeWithCoder:coder];
 
562
        if (coll) {
 
563
                [super setFrame:frame];
 
564
        }
 
565
        if ([coder allowsKeyedCoding]) {
 
566
                [coder encodeObject:identifier forKey:@"identifier"];
 
567
                [coder encodeInt:tag forKey:@"tag"];
 
568
                [coder encodeFloat:minDimension forKey:@"minDimension"];
 
569
                [coder encodeFloat:maxDimension forKey:@"maxDimension"];
 
570
                [coder encodeDouble:fraction forKey:@"fraction"];
 
571
                [coder encodeBool:canCollapse forKey:@"canCollapse"];
 
572
        } else {
 
573
                [coder encodeObject:identifier];
 
574
                [coder encodeValueOfObjCType:@encode(typeof(tag)) at:&tag];
 
575
                [coder encodeValueOfObjCType:@encode(typeof(minDimension)) at:&minDimension];
 
576
                [coder encodeValueOfObjCType:@encode(typeof(maxDimension)) at:&maxDimension];
 
577
                [coder encodeValueOfObjCType:@encode(typeof(fraction)) at:&fraction];
 
578
                [coder encodeValueOfObjCType:@encode(typeof(canCollapse)) at:&canCollapse];
 
579
        }
 
580
}
 
581
 
 
582
- (id)initWithCoder:(NSCoder*)coder {
 
583
    if ((self = [super initWithCoder:coder])) {
 
584
                fraction = 0.0;
 
585
                canCollapse = NO;
 
586
                notInLimits = NO;
 
587
                minDimension = 1.0;
 
588
                maxDimension = WAYOUT;
 
589
                identifier = @"";
 
590
                actDivider = NSNotFound;
 
591
                canDragWindow = NO;
 
592
                previous = [self frame];
 
593
                savedSize = previous.size;
 
594
                if (previous.origin.x>=WAYOUT) {
 
595
// The subview was collapsed when encoded, so we correct the origin and collapse it.
 
596
                        BOOL ishor = [self splitViewIsHorizontal];
 
597
                        previous.origin.x -= WAYOUT;
 
598
                        DIM(previous.size) = 0.0;
 
599
                        [self setFrameOrigin:previous.origin];
 
600
                        [self setFrameSize:previous.size];
 
601
                }
 
602
                previous = NSZeroRect;
 
603
                if ([coder allowsKeyedCoding]) {
 
604
                        [self setIdentifier:[coder decodeObjectForKey:@"identifier"]];
 
605
                        tag = [coder decodeIntForKey:@"tag"];
 
606
                        minDimension = [coder decodeFloatForKey:@"minDimension"];
 
607
                        maxDimension = [coder decodeFloatForKey:@"maxDimension"];
 
608
                        fraction = [coder decodeDoubleForKey:@"fraction"];
 
609
                        canCollapse = [coder decodeBoolForKey:@"canCollapse"];
 
610
                } else {
 
611
                        [self setIdentifier:[coder decodeObject]];
 
612
                        [coder decodeValueOfObjCType:@encode(typeof(tag)) at:&tag];
 
613
                        [coder decodeValueOfObjCType:@encode(typeof(minDimension)) at:&minDimension];
 
614
                        [coder decodeValueOfObjCType:@encode(typeof(maxDimension)) at:&maxDimension];
 
615
                        [coder decodeValueOfObjCType:@encode(typeof(fraction)) at:&fraction];
 
616
                        [coder decodeValueOfObjCType:@encode(typeof(canCollapse)) at:&canCollapse];
 
617
                }
 
618
        }
 
619
    return self;
 
620
}
 
621
 
 
622
@end
 
623
 
 
624
@implementation RBSplitSubview (RB___SubviewAdditions)
 
625
 
 
626
// This hides/shows the subview without calling adjustSubview.
 
627
- (void)RB___setHidden:(BOOL)flag {
 
628
        [super setHidden:flag];
 
629
}
 
630
 
 
631
// This internal method returns the current animationData. It will always return nil if
 
632
// the receiver isn't the current owner and some other subview is already being animated.
 
633
// Otherwise, if the parameter is YES, a new animation will be started (or the current
 
634
// one will be restarted).
 
635
- (animationData*)RB___animationData:(BOOL)start resize:(BOOL)resize {
 
636
        if (currentAnimation&&(currentAnimation->owner!=self)) {
 
637
// There already is an animation in progress on some other subview.
 
638
                return NULL;
 
639
        }
 
640
        if (start) {
 
641
// We want to start (or restart) an animation.
 
642
                RBSplitView* sv = [self splitView];
 
643
                if (sv) {
 
644
                        float dim = [self dimension];
 
645
// First assume the default time, then ask the delegate.
 
646
                        NSTimeInterval total = dim*(0.2/150.0);
 
647
                        id delegate = [sv delegate];
 
648
                        if ([delegate respondsToSelector:@selector(splitView:willAnimateSubview:withDimension:)]) {
 
649
                                total = [delegate splitView:sv willAnimateSubview:self withDimension:dim];
 
650
                        }
 
651
// No use animating anything shorter than the frametime.
 
652
                        if (total>FRAMETIME) {
 
653
                                if (!currentAnimation) {
 
654
                                        currentAnimation = (animationData*)malloc(sizeof(animationData));
 
655
                                }
 
656
                                if (currentAnimation) {
 
657
                                        currentAnimation->owner = self;
 
658
                                        currentAnimation->stepsDone = 0;
 
659
                                        currentAnimation->elapsedTime = 0.0;
 
660
                                        currentAnimation->dimension = dim;
 
661
                                        currentAnimation->collapsing = ![self isCollapsed];
 
662
                                        currentAnimation->totalTime = total;
 
663
                                        currentAnimation->finishTime = [NSDate timeIntervalSinceReferenceDate]+total;
 
664
                                        currentAnimation->resizing = resize;
 
665
                                        [sv RB___setDragging:YES];
 
666
                                }
 
667
                        } else if (currentAnimation) {
 
668
                                free(currentAnimation);
 
669
                                currentAnimation = NULL;
 
670
                        }
 
671
                }
 
672
        }
 
673
        return currentAnimation;
 
674
}
 
675
 
 
676
// This internal method steps the animation to the next frame.
 
677
- (void)RB___stepAnimation {
 
678
        NSTimeInterval now = [NSDate timeIntervalSinceReferenceDate];
 
679
        animationData* anim = [self RB___animationData:NO resize:NO];
 
680
        if (anim) {
 
681
                RBSplitView* sv = [self splitView];
 
682
                NSTimeInterval remain = anim->finishTime-now;
 
683
                NSRect frame = [self frame];
 
684
                BOOL ishor = [sv isHorizontal];
 
685
// Continuing animation only makes sense if we still have at least FRAMETIME available.
 
686
                if (remain>=FRAMETIME) {
 
687
                        float avg = anim->elapsedTime;
 
688
// We try to keep a record of how long it takes, on the average, to resize and adjust
 
689
// one animation frame.
 
690
                        if (anim->stepsDone) {
 
691
                                avg /= anim->stepsDone;
 
692
                        }
 
693
                        NSTimeInterval delay = MIN(0.0,FRAMETIME-avg);
 
694
// We adjust the new dimension proportionally to how much of the designated time has passed.
 
695
                        float dim = floorf(anim->dimension*(remain-avg)/anim->totalTime);
 
696
                        if (dim>4.0) {
 
697
                                if (!anim->collapsing) {
 
698
                                        dim = anim->dimension-dim;
 
699
                                } 
 
700
                                DIM(frame.size) = dim;
 
701
                                [self RB___setFrame:frame withFraction:0.0 notify:NO];
 
702
                                [sv adjustSubviews];
 
703
                                [self display];
 
704
                                anim->elapsedTime += [NSDate timeIntervalSinceReferenceDate]-now;
 
705
                                ++anim->stepsDone;
 
706
// Schedule a timer to do the next animation step.
 
707
                                [self performSelector:@selector(RB___stepAnimation) withObject:nil afterDelay:delay inModes:[NSArray arrayWithObjects:NSDefaultRunLoopMode,NSModalPanelRunLoopMode,
 
708
                                        NSEventTrackingRunLoopMode,nil]];
 
709
                                return;
 
710
                        }
 
711
                }
 
712
// We're finished, either collapse or expand entirely now.
 
713
                if (anim->collapsing) {
 
714
                        DIM(frame.size) = 0.0;
 
715
                        [self RB___finishCollapse:frame withFraction:anim->dimension/[sv RB___dimensionWithoutDividers]];
 
716
                } else {
 
717
                        float savemin,savemax;
 
718
                        float dim = [self RB___setMinAndMaxTo:anim->dimension savingMin:&savemin andMax:&savemax];
 
719
                        DIM(frame.size) = dim;
 
720
                        [self RB___finishExpand:frame withFraction:0.0];
 
721
                        minDimension = savemin;
 
722
                        maxDimension = savemax;
 
723
                }
 
724
        }
 
725
}
 
726
 
 
727
// This internal method stops the animation, if the receiver is being animated. It will
 
728
// return YES if the animation was stopped.
 
729
- (BOOL)RB___stopAnimation {
 
730
        if (currentAnimation&&(currentAnimation->owner==self)) {
 
731
                [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(RB___stepAnimation) object:nil];
 
732
                free(currentAnimation);
 
733
                currentAnimation = NULL;
 
734
                [[self splitView] RB___setDragging:NO];
 
735
                return YES;
 
736
        }
 
737
        return NO;
 
738
}
 
739
 
 
740
// This internal method returns the actual visible dimension of the subview. Differs from -dimension in
 
741
// that it returns 0.0 if the subview is collapsed.
 
742
- (float)RB___visibleDimension {
 
743
        BOOL ishor = [self splitViewIsHorizontal];
 
744
        NSRect frame = [self frame];
 
745
        return MAX(0.0,DIM(frame.size));
 
746
}
 
747
 
 
748
// This pair of internal methods is used only inside -[RBSplitView adjustSubviews] to copy subview data
 
749
// from and to that method's internal cache.
 
750
- (void)RB___copyIntoCache:(subviewCache*)cache {
 
751
        cache->sub = self;
 
752
        cache->rect = [self frame];
 
753
        cache->size = [self RB___visibleDimension];
 
754
        cache->fraction = fraction;
 
755
        cache->constrain = NO;
 
756
}
 
757
 
 
758
- (void)RB___updateFromCache:(subviewCache*)cache withTotalDimension:(float)value {
 
759
        float dim = [self RB___visibleDimension];
 
760
        if (cache->size>=1.0) {
 
761
// New state is not collapsed.
 
762
                if (dim>=1.0) {
 
763
// Old state was not collapsed, so we just change the frame.
 
764
                        [self RB___setFrame:cache->rect withFraction:cache->fraction notify:YES];
 
765
                } else {
 
766
// Old state was collapsed, so we expand it.
 
767
                        [self RB___finishExpand:cache->rect withFraction:cache->fraction];
 
768
                }
 
769
        } else {
 
770
// New state is collapsed.
 
771
                if (dim>=1.0) {
 
772
// Old state was not collapsed, so we clear first responder and change the frame.
 
773
                        [self RB___clearResponder];
 
774
                        [self RB___finishCollapse:cache->rect withFraction:dim/value];
 
775
                } else {
 
776
// It was collapsed already, but the frame may have changed, so we set it.
 
777
                        [self RB___setFrame:cache->rect withFraction:cache->fraction notify:YES];
 
778
                }
 
779
        }
 
780
}
 
781
 
 
782
// This internal method sets minimum and maximum values to the same value, saves the old values,
 
783
// and returns the new value (which will be limited to the old values).
 
784
- (float)RB___setMinAndMaxTo:(float)value savingMin:(float*)oldmin andMax:(float*)oldmax {
 
785
        *oldmin = [self minDimension];
 
786
        *oldmax = [self maxDimension];
 
787
        if (value<*oldmin) {
 
788
                value = *oldmin;
 
789
        }
 
790
        if (value>*oldmax) {
 
791
                value = *oldmax;
 
792
        }
 
793
        minDimension = maxDimension = value;
 
794
        return value;
 
795
}
 
796
 
 
797
// This internal method tries to clear the first responder, if the current responder is a descendant of
 
798
// the receiving subview. If so, it will set first responder to nil, redisplay the former responder and
 
799
// return YES. Returns NO otherwise.
 
800
- (BOOL)RB___clearResponder {
 
801
        NSWindow* window = [self window];
 
802
        if (window) {
 
803
                NSView* responder = (NSView*)[window firstResponder];
 
804
                if (responder&&[responder respondsToSelector:@selector(isDescendantOf:)]) {
 
805
                        if ([responder isDescendantOf:self]) {
 
806
                                if ([window makeFirstResponder:nil]) {
 
807
                                        [responder display];
 
808
                                        return YES;
 
809
                                }
 
810
                        }
 
811
                }
 
812
        }
 
813
        return NO;
 
814
}
 
815
 
 
816
// This internal method collapses a subview.
 
817
// It returns the negative of the size of the subview before collapsing, or 0.0 if it wasn't collapsed.
 
818
- (float)RB___collapse {
 
819
        float result = 0.0;
 
820
        if (![self isCollapsed]) {
 
821
                RBSplitView* sv = [self splitView];
 
822
                if (sv&&[self canCollapse]) {
 
823
                        [self RB___clearResponder];
 
824
                        NSRect frame = [self frame];
 
825
                        BOOL ishor = [sv isHorizontal];
 
826
                        result = DIM(frame.size);
 
827
// For collapsed views, fraction will contain the fraction of the dimension previously occupied
 
828
                        DIM(frame.size) = 0.0;
 
829
                        [self RB___finishCollapse:frame withFraction:result/[sv RB___dimensionWithoutDividers]];
 
830
                }
 
831
        }
 
832
        return -result;
 
833
}
 
834
 
 
835
// This internal method finishes the collapse of a subview, stopping the animation if
 
836
// there is one, and calling the delegate method if there is one.
 
837
- (void)RB___finishCollapse:(NSRect)rect withFraction:(double)value {
 
838
        RBSplitView* sv = [self splitView];
 
839
        BOOL finish = [self RB___stopAnimation];
 
840
        [self RB___setFrame:rect withFraction:value notify:YES];
 
841
        [sv RB___setMustClearFractions];
 
842
        if (finish) {
 
843
                [self display];
 
844
        }
 
845
        id delegate = [sv delegate];
 
846
        if ([delegate respondsToSelector:@selector(splitView:didCollapse:)]) {
 
847
                [delegate splitView:sv didCollapse:self];
 
848
        }
 
849
}
 
850
 
 
851
// This internal method expands a subview. setToMinimum will usually be YES during a divider drag.
 
852
// It returns the size of the subview after expanding, or 0.0 if it wasn't expanded.
 
853
- (float)RB___expandAndSetToMinimum:(BOOL)setToMinimum {
 
854
        float result = 0.0;
 
855
        RBSplitView* sv = [self splitView];
 
856
        if (sv&&[self isCollapsed]) {
 
857
                NSRect frame = [super frame];
 
858
                double frac = fraction;
 
859
                BOOL ishor = [sv isHorizontal];
 
860
                if (setToMinimum) {
 
861
                        result = DIM(frame.size) = minDimension;
 
862
                } else {
 
863
                        result = [sv RB___dimensionWithoutDividers]*frac;
 
864
// We need to apply a compensation factor for proportional resizing in adjustSubviews.
 
865
                        float newdim = floorf((frac>=1.0)?result:result/(1.0-frac));
 
866
                        DIM(frame.size) = newdim;
 
867
                        result = floorf(result);
 
868
                }
 
869
                [self RB___finishExpand:frame withFraction:0.0];
 
870
        }
 
871
        return result;
 
872
}
 
873
 
 
874
// This internal method finishes the the expansion of a subview, stopping the animation if
 
875
// there is one, and calling the delegate method if there is one.
 
876
- (void)RB___finishExpand:(NSRect)rect withFraction:(double)value {
 
877
        RBSplitView* sv = [self splitView];
 
878
        BOOL finish = [self RB___stopAnimation];
 
879
        [self RB___setFrame:rect withFraction:value notify:YES];
 
880
        [sv RB___setMustClearFractions];
 
881
        if (finish) {
 
882
                [self display];
 
883
        }
 
884
        id delegate = [sv delegate];
 
885
        if ([delegate respondsToSelector:@selector(splitView:didExpand:)]) {
 
886
                [delegate splitView:sv didExpand:self];
 
887
        }
 
888
}
 
889
 
 
890
// These internal methods set the subview's frame or size, and also store a fraction value
 
891
// which is used to ensure repeatability when the whole split view is resized.
 
892
- (void)RB___setFrame:(NSRect)rect withFraction:(double)value notify:(BOOL)notify {
 
893
        RBSplitView* sv = [self splitView];
 
894
        id delegate = nil;
 
895
        if (notify) {
 
896
                delegate = [sv delegate];
 
897
// If the delegate method isn't implemented, we ignore the delegate altogether.
 
898
                if ([delegate respondsToSelector:@selector(splitView:changedFrameOfSubview:from:to:)]) {
 
899
// If the rects are equal, the delegate isn't called.
 
900
                        if (NSEqualRects(previous,rect)) {
 
901
                                delegate = nil;
 
902
                        }
 
903
                } else {
 
904
                        delegate = nil;
 
905
                }
 
906
        }
 
907
        [sv setMustAdjust];
 
908
        [self setFrame:rect];
 
909
        fraction = value;
 
910
        [delegate splitView:sv changedFrameOfSubview:self from:previous to:rect];
 
911
        previous = delegate?rect:NSZeroRect;
 
912
}
 
913
 
 
914
- (void)RB___setFrameSize:(NSSize)size withFraction:(double)value {
 
915
        [[self splitView] setMustAdjust];
 
916
        [self setFrameSize:size];
 
917
        fraction = value;
 
918
}
 
919
 
 
920
// This internal method gets the fraction value.
 
921
- (double)RB___fraction {
 
922
        return fraction;
 
923
}
 
924
 
 
925
@end
 
926