1/** <title>NSDrawer</title>
2
3   <abstract>The drawer class</abstract>
4
5   Copyright (C) 2001 Free Software Foundation, Inc.
6
7   Author: Douglas Simons <doug.simons@testplant.com>
8   Date: 2009
9   Author: Gregory Casamento <greg_casamento@yahoo.com>
10   Date: 2006
11   Author: Fred Kiefer <FredKiefer@gmx.de>
12   Date: 2001
13
14   This file is part of the GNUstep GUI Library.
15
16   This library is free software; you can redistribute it and/or
17   modify it under the terms of the GNU Lesser General Public
18   License as published by the Free Software Foundation; either
19   version 2 of the License, or (at your option) any later version.
20
21   This library is distributed in the hope that it will be useful,
22   but WITHOUT ANY WARRANTY; without even the implied warranty of
23   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the GNU
24   Lesser General Public License for more details.
25
26   You should have received a copy of the GNU Lesser General Public
27   License along with this library; see the file COPYING.LIB.
28   If not, see <http://www.gnu.org/licenses/> or write to the
29   Free Software Foundation, 51 Franklin Street, Fifth Floor,
30   Boston, MA 02110-1301, USA.
31*/
32
33#import <Foundation/NSCoder.h>
34#import <Foundation/NSArchiver.h>
35#import <Foundation/NSKeyedArchiver.h>
36#import <Foundation/NSNotification.h>
37#import <Foundation/NSException.h>
38#import <Foundation/NSThread.h>
39#import <Foundation/NSTimer.h>
40#import "AppKit/NSWindow.h"
41#import "AppKit/NSBox.h"
42#import "AppKit/NSView.h"
43#import "AppKit/NSDrawer.h"
44#import "AppKit/NSGraphics.h"
45
46static NSNotificationCenter *nc = nil;
47
48@interface GSDrawerWindow : NSWindow
49{
50  NSWindow *_parentWindow;
51  NSWindow *_pendingParentWindow;
52  NSDrawer *_drawer;
53  id        _container;
54  NSBox    *_borderBox;
55  NSSize   _borderSize;
56  NSTimer  *_timer;
57  NSRect   _latestParentFrame;
58  BOOL     _wasOpen;
59}
60- (NSRect) frameFromParentWindowFrameInState:(NSInteger)state;
61
62// open/close
63- (void) openOnEdge;
64- (void) closeOnEdge;
65- (void) slideOpen:(BOOL)opening;
66- (void) startTimer;
67- (void) stopTimer;
68
69// window/drawer properties
70- (void) setParentWindow: (NSWindow *)window;
71- (NSWindow *) parentWindow;
72- (void) setPendingParentWindow: (NSWindow *)window;
73- (NSWindow *) pendingParentWindow;
74- (void) setDrawer: (NSDrawer *)drawer;
75- (NSDrawer *) drawer;
76
77// handle notifications...
78- (void) handleWindowDidBecomeKey: (NSNotification *)notification;
79- (void) handleWindowClose: (NSNotification *)notification;
80- (void) handleWindowMiniaturize: (NSNotification *)notification;
81- (void) handleWindowDeminiaturize: (NSNotification *)notification;
82- (void) handleWindowMove: (NSNotification *)notification;
83@end
84
85@implementation GSDrawerWindow
86+ (void) initialize
87{
88  if (self == [GSDrawerWindow class])
89    {
90      nc = [NSNotificationCenter defaultCenter];
91      [self setVersion: 0];
92    }
93}
94
95- (id) initWithContentRect: (NSRect)contentRect
96		 styleMask: (NSUInteger)aStyle
97		   backing: (NSBackingStoreType)bufferingType
98		     defer: (BOOL)flag
99{
100  if(NSIsEmptyRect(contentRect))
101    {
102      contentRect = NSMakeRect(0,0,100,100);
103    }
104
105  self = [super initWithContentRect: contentRect
106		styleMask: aStyle
107		backing: bufferingType
108		defer: flag];
109  if (self != nil)
110    {
111      NSRect rect = contentRect;
112      NSRect border = contentRect;
113      NSSize containerContentSize;
114
115      rect.origin.x += 6;
116      rect.origin.y += 6;
117      rect.size.width -= 16;
118      rect.size.height -= 16;
119
120      border.origin.x += 1;
121      border.origin.y += 1;
122      border.size.width -= 2;
123      border.size.height -= 2;
124
125      _borderBox = [[NSBox alloc] initWithFrame: border];
126      [_borderBox setTitle: @""];
127      [_borderBox setTitlePosition: NSNoTitle];
128      [_borderBox setBorderType: NSLineBorder];
129      [_borderBox setAutoresizingMask: NSViewWidthSizable | NSViewHeightSizable];
130      [_borderBox setContentViewMargins: NSMakeSize(0,0)];
131      [[super contentView] addSubview: _borderBox];
132
133      _container = [[NSBox alloc] initWithFrame: rect];
134      [_container setTitle: @""];
135      [_container setTitlePosition: NSNoTitle];
136      [_container setBorderType: NSBezelBorder];
137      [_container setAutoresizingMask: NSViewWidthSizable | NSViewHeightSizable];
138      [_container setContentViewMargins: NSMakeSize(2,2)];
139      [_borderBox addSubview: _container];
140
141      // determine the difference between the container's content size and the window content size
142      containerContentSize = [[_container contentView] frame].size;
143      _borderSize = NSMakeSize(contentRect.size.width - containerContentSize.width,
144				contentRect.size.height - containerContentSize.height);
145    }
146  return self;
147}
148
149- (id) container
150{
151  return _container;
152}
153
154- (NSRect) frameFromParentWindowFrameInState:(NSInteger)state
155{
156  NSRect newFrame = [_parentWindow frame];
157  CGFloat totalOffset = [_drawer leadingOffset] + [_drawer trailingOffset];
158  NSRectEdge edge = [_drawer preferredEdge];
159  BOOL opened = (state == NSDrawerOpenState || state == NSDrawerOpeningState);
160  NSSize size = [_parentWindow frame].size; // [_drawer maxContentSize];
161  NSRect windowContentRect = [[_parentWindow contentView] frame];
162  CGFloat windowHeightWithoutTitleBar = windowContentRect.origin.y + windowContentRect.size.height; // FIXME: This should probably add the toolbar height too, if the window has a toolbar
163
164  if (edge == NSMinXEdge) // left
165    {
166      if (opened)
167        newFrame.size.width = [_drawer minContentSize].width + _borderSize.width;
168      else
169        newFrame.size.width = 16;
170
171      newFrame.size.height = windowHeightWithoutTitleBar - totalOffset;
172      newFrame.origin.y += [_drawer trailingOffset];
173      if (opened)
174        newFrame.origin.x -= newFrame.size.width;
175    }
176  else if (edge == NSMinYEdge) // bottom
177    {
178      if (opened)
179        newFrame.size.height = [_drawer minContentSize].height + _borderSize.height;
180      else
181        newFrame.size.height = 16;
182
183      newFrame.size.width -= totalOffset;
184      newFrame.origin.x += [_drawer leadingOffset];
185      if (opened)
186        newFrame.origin.y -= newFrame.size.height;
187    }
188  else if (edge == NSMaxXEdge) // right
189    {
190      if (opened)
191        newFrame.size.width = [_drawer minContentSize].width + _borderSize.width;
192      else
193        newFrame.size.width = 16;
194
195      newFrame.size.height = windowHeightWithoutTitleBar - totalOffset;
196      newFrame.origin.y += [_drawer trailingOffset];
197      newFrame.origin.x += size.width;
198      if (!opened)
199        newFrame.origin.x -= newFrame.size.width;
200    }
201  else if (edge == NSMaxYEdge) // top
202    {
203      if (opened)
204        newFrame.size.height = [_drawer minContentSize].height + _borderSize.height;
205      else
206        newFrame.size.height = 16;
207
208      newFrame.size.width -= totalOffset;
209      newFrame.origin.x += [_drawer leadingOffset];
210      newFrame.origin.y += size.height; // put above the window
211      if (!opened)
212        newFrame.origin.y -= newFrame.size.height;
213    }
214
215  return newFrame;
216}
217
218
219
220- (BOOL) canBecomeKeyWindow
221{
222  return YES;
223}
224
225- (BOOL) canBecomeMainWindow
226{
227  return NO;
228}
229
230- (void) becomeKeyWindow
231{
232      [_parentWindow orderFrontRegardless]; // so clicking on the drawer will bring the parent to the front
233      [super becomeKeyWindow];
234}
235
236/*
237- (void) orderFront: (id)sender
238{
239  NSPoint holdOrigin = [self frame].origin;
240  NSPoint newOrigin = NSMakePoint(-10000,-10000);
241  NSRect tempFrame = [self frame];
242
243  // order the window under the parent...
244  tempFrame.origin = newOrigin;
245  [self setFrame: tempFrame display: NO];
246  [super orderFront: sender];
247  // [_parentWindow orderWindow: NSWindowAbove relativeTo: [self windowNumber]];
248  tempFrame.origin = holdOrigin;
249  // [self setFrame: tempFrame display: YES];
250}
251*/
252
253/*
254- (void) orderFront: (id)sender
255{
256  [super orderWindow:NSWindowBelow relativeTo:[_parentWindow windowNumber]];
257}
258*/
259
260- (void) startTimer
261{
262  NSTimeInterval time = 0.1;
263  _timer = [NSTimer scheduledTimerWithTimeInterval: time
264		    target: self
265		    selector: @selector(_timedWindowReset)
266		    userInfo: nil
267		    repeats: YES];
268}
269
270- (void) stopTimer
271{
272  [_timer invalidate];
273  _timer = nil;
274}
275
276- (void) orderFrontRegardless
277{
278  [self orderFront: self];
279}
280
281- (void) orderOut: (id)sender
282{
283  [super orderOut: sender];
284}
285
286/*
287- (void) orderWindow: (NSWindowOrderingMode)place relativeTo: (int)windowNum
288{
289  NSLog(@"Ordering window....");
290  [super orderWindow: place relativeTo: windowNum];
291}
292*/
293
294- (void) lockBorderBoxForSliding
295{
296  // set the _borderBox to not resize during the slide, and attach it to the appropriate edge instead
297  NSRectEdge edge = [_drawer preferredEdge];
298  NSUInteger resizeMask = 0;
299  if (edge == NSMinXEdge) // left
300    {
301      resizeMask = NSViewMaxXMargin;
302    }
303  else if (edge == NSMinYEdge) // bottom
304    {
305      resizeMask = NSViewMaxYMargin;
306    }
307  else if (edge == NSMaxXEdge) // right
308    {
309      resizeMask = NSViewMinXMargin;
310    }
311  else if (edge == NSMaxYEdge) // top
312    {
313      resizeMask = NSViewMinYMargin;
314    }
315  [_borderBox setAutoresizingMask: resizeMask]; // don't resize -- just give appearance of sliding
316}
317
318- (void) unlockBorderBoxAfterSliding
319{
320  // set the _borderBox to resize again
321  [_borderBox setAutoresizingMask: NSViewWidthSizable | NSViewHeightSizable];
322}
323
324- (void) openOnEdge
325{
326  // prepare drawer contents before sliding...
327  NSRect frame = [self frameFromParentWindowFrameInState:NSDrawerOpenState];
328  [self setFrame:frame display: YES]; // make sure it's the full (open) size before locking the borderBox
329  if ([_parentWindow isVisible]) // don't order front until parent window is visible
330    {
331      [self lockBorderBoxForSliding];
332      [self orderFront: self];
333      [self slideOpen:YES];
334      [self performSelector:@selector(unlockBorderBoxAfterSliding) withObject:nil afterDelay:0.01];
335    }
336  [self startTimer];
337}
338
339- (void) closeOnEdge
340{
341  NSRect frame;
342
343  [self stopTimer];
344  [self lockBorderBoxForSliding];
345  [self slideOpen:NO];
346  [self orderOut: self];
347
348  frame = [self frameFromParentWindowFrameInState:NSDrawerOpenState];
349  [self setFrame:frame display: YES]; // make sure it's the full (open) size again (offscreen) before unlocking
350  [self performSelector:@selector(unlockBorderBoxAfterSliding) withObject:nil afterDelay:0.01];
351
352  if (_pendingParentWindow != nil
353    && _pendingParentWindow != _parentWindow)
354    {
355      [self setParentWindow: _pendingParentWindow];
356      ASSIGN(_pendingParentWindow, nil);
357    }
358}
359
360- (void) slideOpen:(BOOL)opening
361{
362  NSRect frame = [self frameFromParentWindowFrameInState:(opening?NSDrawerClosedState:NSDrawerOpenState)];
363  NSRect newFrame = [self frameFromParentWindowFrameInState:(opening?NSDrawerOpenState:NSDrawerClosedState)];
364  NSTimeInterval slideDelay = 0.03;
365  NSDate *nextStop = [NSDate dateWithTimeIntervalSinceNow:slideDelay];
366  int count = 10;
367  CGFloat deltaX = (newFrame.origin.x - frame.origin.x) / count;
368  CGFloat deltaY = (newFrame.origin.y - frame.origin.y) / count;
369  CGFloat deltaW = (newFrame.size.width - frame.size.width) / count;
370  CGFloat deltaH = (newFrame.size.height - frame.size.height) / count;
371  while (count--)
372    {
373      frame.origin.x += deltaX;
374      frame.origin.y += deltaY;
375      frame.size.width += deltaW;
376      frame.size.height += deltaH;
377      [self setFrame: frame display: YES];
378      [NSThread sleepUntilDate:nextStop];
379      nextStop = [nextStop addTimeInterval:slideDelay];
380    }
381  [self setFrame:newFrame display: YES];
382}
383
384- (void) _resetWindowPosition
385{
386  if ([_parentWindow isVisible]) // don't set our frame until parent window is visible
387    {
388      NSRect frame = [self frameFromParentWindowFrameInState:[_drawer state]];
389      [self setFrame: frame display: YES];
390      if (![self isVisible] && [_drawer state] != NSDrawerClosedState)
391        [self orderFront:self];
392    }
393  if ([self isVisible] && [_parentWindow isKeyWindow]) // do our best to maintain proper window ordering
394    {
395      [super orderFrontRegardless];
396      [_parentWindow orderFront:self];
397    }
398}
399
400- (void) _timedWindowReset
401{
402  NSRect frame = [_parentWindow frame];
403  if (!NSEqualRects(frame, _latestParentFrame))
404    {
405      [self _resetWindowPosition];
406      _latestParentFrame = frame;
407    }
408}
409
410- (void) handleWindowClose: (NSNotification *)notification
411{
412  [self stopTimer];
413  [self close];
414}
415
416- (void) handleWindowMiniaturize: (NSNotification *)notification
417{
418  _wasOpen = ([_drawer state] == NSDrawerOpenState);
419  if (_wasOpen)
420    {
421      // It would be nice to call [self closeOnEdge] here, but the parent window is already closing
422      // (with animation) so it doesn't look right to slide the drawer. So we'll do this instead:
423      [self stopTimer];
424      [self close];
425    }
426}
427
428- (void) handleWindowDeminiaturize: (NSNotification *)notification
429{
430  if (_wasOpen)
431    {
432      //[self openOnEdge]; -- this also doesn't work: see comment above
433      _latestParentFrame = NSMakeRect(0,0,0,0);
434      [self startTimer];
435    }
436}
437
438- (void) handleWindowMove: (NSNotification *)notification
439{
440  [self _resetWindowPosition];
441}
442
443- (void) handleWindowDidBecomeKey: (NSNotification *)notification
444{
445  if([_drawer state] == NSDrawerOpenState)
446    {
447      [self _resetWindowPosition];
448    }
449}
450
451- (void) setParentWindow: (NSWindow *)window
452{
453  if (_parentWindow != window)
454    {
455      [super setParentWindow: window];
456      ASSIGN(_parentWindow, window);
457      [nc removeObserver: self];
458
459      if (_parentWindow != nil)
460	{
461	  [self _resetWindowPosition];
462
463	  // add observers....
464	  [nc addObserver: self
465	      selector: @selector(handleWindowClose:)
466	      name: NSWindowWillCloseNotification
467	      object: _parentWindow];
468
469	  [nc addObserver: self
470	      selector: @selector(handleWindowMiniaturize:)
471	      name: NSWindowWillMiniaturizeNotification
472	      object: _parentWindow];
473
474	  [nc addObserver: self
475	      selector: @selector(handleWindowDeminiaturize:)
476	      name: NSWindowDidDeminiaturizeNotification
477	      object: _parentWindow];
478
479	  [nc addObserver: self
480	      selector: @selector(handleWindowMove:)
481	      name: NSWindowWillMoveNotification
482	      object: _parentWindow];
483
484	  [nc addObserver: self
485	      selector: @selector(handleWindowMove:)
486	      name: NSWindowDidResizeNotification
487	      object: _parentWindow];
488
489	  [nc addObserver: self
490	      selector: @selector(handleWindowDidBecomeKey:)
491	      name: NSWindowDidBecomeKeyNotification
492	      object: _parentWindow];
493	}
494    }
495}
496
497- (NSWindow *) parentWindow
498{
499  return _parentWindow;
500}
501
502- (void) setPendingParentWindow: (NSWindow *)window
503{
504  ASSIGN(_pendingParentWindow, window);
505}
506
507- (NSWindow *) pendingParentWindow
508{
509  return _pendingParentWindow;
510}
511
512- (void) setDrawer: (NSDrawer *)drawer
513{
514  // don't retain, since the drawer retains us...
515  _drawer = drawer;
516}
517
518- (NSDrawer *) drawer
519{
520  return _drawer;
521}
522
523- (void) dealloc
524{
525  [self stopTimer];
526  RELEASE(_parentWindow);
527  RELEASE(_borderBox);
528  TEST_RELEASE(_pendingParentWindow);
529  [super dealloc];
530}
531@end
532
533@implementation NSDrawer
534
535+ (void) initialize
536{
537  if (self == [NSDrawer class])
538    {
539      nc = [NSNotificationCenter defaultCenter];
540      [self setVersion: 0];
541    }
542}
543
544// Creation
545- (id) init
546{
547  return [self initWithContentSize: NSMakeSize(100,100)
548	       preferredEdge: NSMinXEdge];
549}
550
551- (id) initWithContentSize: (NSSize)contentSize
552	     preferredEdge: (NSRectEdge)edge
553{
554  self = [super init];
555  // initialize the drawer window...
556  _drawerWindow =  [[GSDrawerWindow alloc]
557		     initWithContentRect: NSMakeRect(0, 0, contentSize.width,
558						     contentSize.height)
559		     styleMask: 0
560		     backing: NSBackingStoreBuffered
561		     defer: NO];
562  [_drawerWindow setDrawer: self];
563  [_drawerWindow setReleasedWhenClosed: NO];
564
565  _preferredEdge = edge;
566  _currentEdge = edge;
567  _maxContentSize = NSMakeSize(200,200);
568  _minContentSize = contentSize; //NSMakeSize(50,50);
569  if (edge == NSMinXEdge || edge == NSMaxXEdge) {
570    _leadingOffset = 0.0; // for side drawers, top of drawer is immediately below the title bar
571    _trailingOffset = 10.0;
572  } else {
573    _leadingOffset = 10.0;
574    _trailingOffset = 10.0;
575  }
576  _state = NSDrawerClosedState;
577
578  return self;
579}
580
581- (void) dealloc
582{
583  RELEASE(_drawerWindow);
584  if (_delegate != nil)
585    {
586      [nc removeObserver: _delegate name: nil object: self];
587      _delegate = nil;
588    }
589  [super dealloc];
590}
591
592// Opening and Closing
593- (void) close
594{
595  if (_state != NSDrawerOpenState)
596    return;
597
598  if ((_delegate != nil)
599    && ([_delegate respondsToSelector: @selector(drawerShouldClose:)])
600    && ![_delegate drawerShouldClose: self])
601    return;
602
603  _state = NSDrawerClosingState;
604  [nc postNotificationName: NSDrawerWillCloseNotification object: self];
605
606   [_drawerWindow closeOnEdge];
607
608  _state = NSDrawerClosedState;
609  [nc postNotificationName: NSDrawerDidCloseNotification object: self];
610}
611
612- (void) close: (id)sender
613{
614  [self close];
615}
616
617- (void) open
618{
619  [self openOnEdge: _preferredEdge];
620}
621
622- (void) open: (id)sender
623{
624  [self open];
625}
626
627- (void) openOnEdge: (NSRectEdge)edge
628{
629  if ((_state != NSDrawerClosedState)
630    || ([self parentWindow] == nil))
631    return;
632
633  if ((_delegate != nil)
634    && ([_delegate respondsToSelector: @selector(drawerShouldOpen:)])
635    && ![_delegate drawerShouldOpen: self])
636    return;
637
638  _state = NSDrawerOpeningState;
639  [nc postNotificationName: NSDrawerWillOpenNotification object: self];
640
641  _currentEdge = edge;
642  [_drawerWindow openOnEdge];
643
644  _state = NSDrawerOpenState;
645  [nc postNotificationName: NSDrawerDidOpenNotification object: self];
646}
647
648- (void) toggle: (id)sender
649{
650  if (_state == NSDrawerClosedState)
651    [self open: sender];
652  else if (_state == NSDrawerOpenState)
653    [self close: sender];
654  // Do nothing for inbetween states
655}
656
657// Managing Size
658- (NSSize) contentSize
659{
660  return [[_drawerWindow contentView] frame].size;
661}
662
663- (CGFloat) leadingOffset
664{
665  return _leadingOffset;
666}
667
668- (NSSize) maxContentSize
669{
670  return _maxContentSize;
671}
672
673- (NSSize) minContentSize
674{
675  return _minContentSize;
676}
677
678- (void) setContentSize: (NSSize)size
679{
680  // Check with min and max size
681  if (size.width < _minContentSize.width)
682    size.width = _minContentSize.width;
683  if (size.height < _minContentSize.height)
684    size.height = _minContentSize.height;
685  if (size.width > _maxContentSize.width)
686    size.width = _maxContentSize.width;
687  if (size.height > _maxContentSize.height)
688    size.height = _maxContentSize.height;
689
690  // Check with delegate
691  if ((_delegate != nil)
692    && ([_delegate respondsToSelector:
693      @selector(drawerWillResizeContents:toSize:)]))
694    {
695      size = [_delegate drawerWillResizeContents: self
696					  toSize: size];
697    }
698
699  [_drawerWindow setContentSize: size];
700}
701
702- (void) setLeadingOffset: (CGFloat)offset
703{
704  _leadingOffset = offset;
705}
706
707- (void) setMaxContentSize: (NSSize)size
708{
709  _maxContentSize = size;
710}
711
712- (void) setMinContentSize: (NSSize)size
713{
714  _minContentSize = size;
715}
716
717- (void) setTrailingOffset: (CGFloat)offset
718{
719  _trailingOffset = offset;
720}
721
722- (CGFloat) trailingOffset
723{
724  return _trailingOffset;
725}
726
727// Managing Edge
728- (NSRectEdge) edge
729{
730  return _currentEdge;
731}
732
733- (NSRectEdge) preferredEdge
734{
735  return _preferredEdge;
736}
737
738- (void) setPreferredEdge: (NSRectEdge)preferredEdge
739{
740  _preferredEdge = preferredEdge;
741}
742
743// Managing Views
744- (NSView *) contentView
745{
746  return [[_drawerWindow container] contentView];
747}
748
749- (NSWindow *) parentWindow
750{
751  return [_drawerWindow parentWindow];
752}
753
754- (void) setContentView: (NSView *)aView
755{
756  [[_drawerWindow container] setContentView: aView];
757}
758
759- (void) setParentWindow: (NSWindow *)parent
760{
761  if (_state == NSDrawerClosedState)
762    {
763      [_drawerWindow setParentWindow: parent];
764    }
765  else
766    {
767      [_drawerWindow setPendingParentWindow: parent];
768    }
769}
770
771
772// Delegation and State
773- (id) delegate
774{
775  return _delegate;
776}
777
778- (void) setDelegate: (id)anObject
779{
780  if (_delegate)
781    {
782      [nc removeObserver: _delegate name: nil object: self];
783    }
784
785  _delegate = anObject;
786
787#define SET_DELEGATE_NOTIFICATION(notif_name) \
788  if ([_delegate respondsToSelector: @selector(drawer##notif_name:)]) \
789    [nc addObserver: _delegate \
790      selector: @selector(drawer##notif_name:) \
791      name: NSDrawer##notif_name##Notification object: self]
792
793  SET_DELEGATE_NOTIFICATION(DidClose);
794  SET_DELEGATE_NOTIFICATION(DidOpen);
795  SET_DELEGATE_NOTIFICATION(WillClose);
796  SET_DELEGATE_NOTIFICATION(WillOpen);
797}
798
799- (NSInteger) state
800{
801  return _state;
802}
803
804/*
805 * NSCoding protocol
806 */
807- (void) encodeWithCoder: (NSCoder*)aCoder
808{
809  id parent = [self parentWindow];
810
811  [super encodeWithCoder: aCoder];
812  if ([aCoder allowsKeyedCoding])
813    {
814      [aCoder encodeSize: [self contentSize] forKey: @"NSContentSize"];
815
816      if (_delegate != nil)
817	{
818	  [aCoder encodeObject: _delegate forKey: @"NSDelegate"];
819	}
820
821      [aCoder encodeFloat: _leadingOffset forKey: @"NSLeadingOffset"];
822      [aCoder encodeSize: _maxContentSize forKey: @"NSMaxContentSize"];
823      [aCoder encodeSize: _minContentSize forKey: @"NSMinContentSize"];
824
825      if (parent != nil)
826	{
827	  [aCoder encodeObject: parent forKey: @"NSParentWindow"];
828	}
829
830      [aCoder encodeInt: _preferredEdge forKey: @"NSPreferredEdge"];
831      [aCoder encodeFloat: _trailingOffset forKey: @"NSTrailingOffset"];
832    }
833  else
834    {
835      [aCoder encodeSize: [self contentSize]];
836      [aCoder encodeObject: _delegate];
837      [aCoder encodeValueOfObjCType: @encode(float) at: &_leadingOffset];
838      [aCoder encodeSize: _maxContentSize];
839      [aCoder encodeSize: _minContentSize];
840      [aCoder encodeObject: parent];
841      [aCoder encodeValueOfObjCType: @encode(unsigned) at: &_preferredEdge];
842      [aCoder encodeValueOfObjCType: @encode(float) at: &_trailingOffset];
843    }
844}
845
846- (id) initWithCoder: (NSCoder*)aDecoder
847{
848  if ((self = [super initWithCoder: aDecoder]) != nil)
849    {
850      NSWindow *parentWindow = nil;
851
852      if ([aDecoder allowsKeyedCoding])
853	{
854	  _contentSize = [aDecoder decodeSizeForKey: @"NSContentSize"];
855
856	  if ([aDecoder containsValueForKey: @"NSDelegate"])
857	    {
858	      ASSIGN(_delegate, [aDecoder decodeObjectForKey: @"NSDelegate"]);
859	    }
860
861	  _leadingOffset = [aDecoder decodeFloatForKey: @"NSLeadingOffset"];
862	  _maxContentSize = [aDecoder decodeSizeForKey: @"NSMaxContentSize"];
863	  _minContentSize = [aDecoder decodeSizeForKey: @"NSMinContentSize"];
864
865	  if ([aDecoder containsValueForKey: @"NSParentWindow"])
866	    {
867	      parentWindow = [aDecoder decodeObjectForKey: @"NSParentWindow"];
868	    }
869
870	  _preferredEdge = [aDecoder decodeIntForKey: @"NSPreferredEdge"];
871	  _trailingOffset = [aDecoder decodeFloatForKey: @"NSTrailingOffset"];
872	}
873      else
874	{
875	  int version = [aDecoder versionForClassName: @"NSDrawer"];
876	  if (version == 0)
877	    {
878	      _contentSize = [aDecoder decodeSize];
879	      ASSIGN(_delegate, [aDecoder decodeObject]);
880	      [aDecoder decodeValueOfObjCType: @encode(float)
881					   at: &_leadingOffset];
882	      _maxContentSize = [aDecoder decodeSize];
883	      _minContentSize = [aDecoder decodeSize];
884	      parentWindow = [aDecoder decodeObject];
885	      [aDecoder decodeValueOfObjCType: @encode(unsigned)
886					   at: &_preferredEdge];
887	      [aDecoder decodeValueOfObjCType: @encode(float)
888					   at: &_trailingOffset];
889	    }
890	  else
891	    {
892	      [NSException raise: NSInternalInconsistencyException
893		format: @"Invalid version of NSDrawer (version = %d).",
894		version];
895	      return nil; // not reached, but keeps gcc happy...
896	    }
897	}
898
899      // set up drawer...
900      _drawerWindow =  [[GSDrawerWindow alloc]
901			 initWithContentRect:
902			   NSMakeRect(0, 0,_contentSize.width,
903				      _contentSize.height)
904			 styleMask: 0
905			 backing: NSBackingStoreBuffered
906			 defer: NO];
907      [_drawerWindow setParentWindow: parentWindow];
908      [_drawerWindow setDrawer: self];
909      [_drawerWindow setReleasedWhenClosed: NO];
910
911      // initial state...
912      _state = NSDrawerClosedState;
913
914    }
915  return self;
916}
917
918@end
919