/** <title>PKPanesController</title>

	PKPanesController.m

	<abstract>Pane window controller class</abstract>

	Copyright (C) 2006 Yen-Ju Chen 
	Copyright (C) 2004 Quentin Mathe
                           Uli Kusterer

	Author:  Yen-Ju Chen <yjchenx gmail>
	Author:  Quentin Mathe <qmathe@club-internet.fr>
                 Uli Kusterer
        Date:  February 2005

	This library is free software; you can redistribute it and/or
	modify it under the terms of the GNU Lesser General Public
	License as published by the Free Software Foundation; either
	version 2.1 of the License, or (at your option) any later version.
 
	This library is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
	Lesser General Public License for more details.
 
	You should have received a copy of the GNU Lesser General Public
	License along with this library; if not, write to the Free Software
	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#import <AppKit/AppKit.h>

#import <PaneKit/CocoaCompatibility.h>
#import <PaneKit/PKPaneRegistry.h>
#import <PaneKit/PKPane.h>
#import <PaneKit/PKPresentationBuilder.h>
#import <PaneKit/PKPanesController.h>
#import "GNUstep.h"

@interface PKPanesController (Private)
- (void) windowWillClose: (NSNotification *) not;
- (NSView *) mainViewWaitSign;
@end

/** <p>PKPanesController Description</p> */
@implementation PKPanesController

- (id) init
{
  /* This is usually called by Nib file.
   * In that case, we assume the owner and registry is
   * connected in Nib. */

  self = [super init];

  return self;
}

/* This is usually called programmingly.
   So we call awakeFromNib explicitly */
- (id) initWithRegistry: (PKPaneRegistry *) r
       presentationMode: (const NSString *) mode
       owner: (id) o
{
  self = [super init];

  ASSIGN(registry, r);
  ASSIGN(owner, o);
  /* Request a builder which matches presentationMode to 
   * presentation backend. */
  ASSIGN(presentation, [PKPresentationBuilder builderForPresentationMode: mode]);

  [self awakeFromNib];

  return self;
}

/* Initialize stuff that can't be set in the nib/gorm file. */
- (void) awakeFromNib
{
  if (owner == nil) {
    /* Create an empty window as owner */
    owner = [[NSPanel alloc] initWithContentRect: NSMakeRect(400, 400, 300, 150)
                           styleMask: NSTitledWindowMask|NSClosableWindowMask
                             backing: NSBackingStoreBuffered 
                               defer: YES];
    [owner setReleasedWhenClosed: NO];
  }
  if ([owner isKindOfClass: [NSWindow class]]) {
    [owner setDelegate: self];
  }

  if (presentation == nil) {
    /* Use toolbar as default */ 
    ASSIGN(presentation, [PKPresentationBuilder builderForPresentationMode: (NSString *)PKToolbarPresentationMode]);
  }
  [presentation setPanesController: self];
    
  /* In subclasses, we set up our list view where preference panes will be
     listed. */
  [presentation loadUI];

  NSArray *prefPanes = [registry loadedPlugins];
    
  if (prefPanes != nil)
  {
    NSString *identifier = 
        [[prefPanes objectAtIndex: 0] objectForKey: @"identifier"];
	
    /* Load a first pane. */
    [self selectPaneWithIdentifier: identifier];
  }
  else
  {
    NSLog(@"No Pane loaded are available.");
  }
}

/*
 * Preference pane related methods
 */

/** <p>Sets or resets up completely the currently selected <em>preference 
   pane</em> UI.</p>
   <p><strong>By being the main bottleneck for switching preference panes, this 
   method must be called each time a new preference pane is selected like with
   -selectPaneWithIdentifier: method.</strong></p> */
- (BOOL) updateUIForPane: (PKPane *) pane
{
  NSView *prefsView = [self view];
  PKPane *nextPane = nil;
  PKPane *requestedPane = nil;
  ASSIGN(requestedPane, pane);
  if (currentPane == pane) {
    return YES;
  }
    
  if (currentPane != nil)  /* Have a previous pane that needs unloading? */
  {
    /* Make sure last text field gets an "end editing" message: */
    if ([currentPane autoSaveTextFields])
      [[prefsView window] selectNextKeyView: self];
		
    if(requestedPane) /* User passed in a new pane to select? */
    {
      switch ([currentPane shouldUnselect]) /* Ask old one to unselect. */
      {
        case NSUnselectCancel:
          DESTROY(nextPane);
          return NO;
        case NSUnselectLater:
          ASSIGN(nextPane, requestedPane); /* Remember next pane for later. */
          return NO;
        case NSUnselectNow:
          DESTROY(nextPane);
          break;
      }
    }
    else 
    {
      NSLog(@"Weird, no current pane andn no requested pane");
      return NO;
    }
		
    /* Unload the old pane: */
    [currentPane willUnselect];
    [[currentPane mainView] removeFromSuperview];
    [currentPane didUnselect];
    DESTROY(currentPane);
  }
	
  /* Display "please wait" message in middle of content area: */
  if (mainViewWaitSign != nil)
  {
    NSRect box = [mainViewWaitSign frame];
    NSRect wBox = [prefsView frame];
    box.origin.x = (int)(abs(wBox.size.width -box.size.width) /2);
    box.origin.y = (int)(abs(wBox.size.height -box.size.height) /2);
    [mainViewWaitSign setFrameOrigin: box.origin];
    [prefsView addSubview: mainViewWaitSign];
    [prefsView setNeedsDisplay: YES];
    [prefsView display];
  }
	
  /* Get main view for next pane: */
  [requestedPane setOwner: self];
  NSView *paneView = [requestedPane mainView];
  // NOTE: By security, we check both frame origin and autoresizing.
  [paneView setFrameOrigin: NSMakePoint(0, 0)];
  [paneView setAutoresizingMask: NSViewNotSizable];
  [requestedPane willSelect];
    
  /* Remove "wait" sign: */
  if (mainViewWaitSign != nil)
    [mainViewWaitSign removeFromSuperview];
	
  /* Resize window so content area is large enough for prefs and show new pane: */
  [presentation layoutPreferencesViewWithPaneView: paneView];
	
  /* Finish up by setting up key views and remembering new current pane: */
  ASSIGN(currentPane, requestedPane);
  // FIXME: The hack below will have to be decently reimplemented in order we
  // we can support not resigning first responder for other presentation
  // views when a new pane gets selected.
  if ([[self presentationMode] isEqual: PKTablePresentationMode] == NO)
    [[prefsView window] makeFirstResponder: [requestedPane initialKeyView]];
  [requestedPane didSelect];
	
  /* Message window title:
  [[prefsView window] setTitle: [dict objectForKey: @"name"]]; */
	
  return YES;
}

/** <p>Switches to <em>preference pane</em> with the given 
    identifier.</p> 
    <p><strong>This method needs to be called in 
    -switchPaneView:.</strong></p> */
- (void) selectPaneWithIdentifier: (NSString *)identifier
{
  /* If the preference pane is already selected, we don't take in account the
     request, especially because it we reloads another instance of the pane 
     view on top of the current one. */
  if ([[self selectedPaneIdentifier] isEqualToString: identifier])
    return;

  PKPane *pane = [registry paneWithIdentifier: identifier];
    
  if ([presentation respondsToSelector: @selector(willSelectPaneWithIdentifier:)])
    [presentation willSelectPaneWithIdentifier: identifier];
    
  [self updateUIForPane: pane];
    
  if ([presentation respondsToSelector: @selector(didSelectPaneWithIdentifier:)])
    [presentation didSelectPaneWithIdentifier: identifier];
}

/*
 * Runtime stuff (ripped from Preferences.app by Jeff Teunissen)
 */

- (BOOL) respondsToSelector: (SEL) aSelector
{
  if (aSelector == NULL)
    return NO;

  if ([super respondsToSelector: aSelector])
    return YES;
    
  if (presentation != nil)
    return [presentation respondsToSelector: aSelector];

  if (currentPane != nil)
    return [currentPane respondsToSelector: aSelector];

  return NO;
}

- (NSMethodSignature *) methodSignatureForSelector: (SEL)aSelector
{
  NSMethodSignature * sign = [super methodSignatureForSelector: aSelector];

  if (sign == nil && currentPane) {
    sign = [(NSObject *)currentPane methodSignatureForSelector: aSelector];
  }

  return sign;
}

- (void) forwardInvocation: (NSInvocation *)invocation
{
  /* First we try to forward messages to our builder. */
  if ([presentation respondsToSelector: [invocation selector]])
    [invocation invokeWithTarget: presentation];
    
  if ([currentPane respondsToSelector: [invocation selector]])
    [invocation invokeWithTarget: currentPane];
}

/*
 * Accessors
 */

/** <p>Returns the view which encloses both preference pane loaded view and 
    presentation view where where every preferences panes are usually listed, it
    is often a window content view.</p> 
    <p>To take an example, for <code>PKToolbarPresentationMode</code>, the view 
    which contains both toolbar view and preference pane dedicated view is 
    returned.</p> */
- (NSView *) view 
{
  if (view == nil && [owner isKindOfClass: [NSWindow class]])
  {
    // FIXME: Hack statement because on GNUstep the view bound to
    // -contentView includes the toolbar view unlike Cocoa (when a
    // toolbar is visible), by the way we have to rely on a special method
    // until GNUstep implementation matches Cocoa better.
        
#ifndef GNUSTEP
    return [(NSWindow *)owner contentView];
#else
    return [(NSWindow *)owner contentViewWithoutToolbar];
#endif
  }
  return view;
}

/** <p>Returns the owner object for the current -view, it
    is usually the parent window in order to allow automatic 
    resizing and window title update when selected preference pane 
    changes.</p> 
    <p>However it is possible to specify an ancestor view when you 
    need to layout <em>preferences view</em> with other views in the 
    content view, but 
    this possibility involves to manage resizing yourself by overriding 
    -[PKPresentationBuilder layoutPreferencesViewWithPaneView:] method.</p> */
- (id) owner
{
  if (owner == nil) {
    /* owner cannot be nil. 
     * It must be initialized programmingly.
     * Call -awakeFromNib to initialize everything.
     */
    [self awakeFromNib];
  }
  return owner;
}

- (PKPaneRegistry *) registry
{
  return registry;
}

/** Returns identifier of the currently selected <em>preference pane</em>. */
- (NSString *) selectedPaneIdentifier
{
  NSArray *plugins = [registry loadedPlugins]; 
  NSDictionary *plugin = [plugins objectWithValue: currentPane forKey: @"instance"];
    
  return [plugin objectForKey: @"identifier"];
}

/** Returns the currently selected <em>preference pane</em>. */
- (PKPane *) selectedPane
{
  return currentPane;
}

/** <p>Returns the <em>wait view</em> displayed between each preference 
    pane switch until UI is fully set up. By default, it displays a circular 
    progress indicator.</p>
    <p><em>Overrides this method if you want to provide to customize such wait view.
    </em></p> */
- (NSView *) mainViewWaitSign
{
  if (mainViewWaitSign == nil)
  {
    // FIXME: We should probably return [self waitView];
    return nil;
  }
  else
  {
    return mainViewWaitSign;
  }
}

/** <p>Returns the <em>presentation mode</em> which is used to identify
    the current presentation style.</p> */
- (const NSString *) presentationMode
{
  return [presentation presentationMode];
}

/** <p>Sets the <em>presentation</em> style used to display the 
    preferences pane list and identified by <var>presentationMode</var>.</p> */
- (void) setPresentationMode: (const NSString *) presentationMode
{
  if ([presentationMode isEqual: [presentation presentationMode]])
    return;
    
  id presentationToCheck = [PKPresentationBuilder builderForPresentationMode: presentationMode];
    
  if (presentationToCheck == nil)
  {
    // FIXME: We may throw an exception here.
  }
  else
  {
    [presentation unloadUI];
    ASSIGN(presentation, presentationToCheck);
    [presentation setPanesController: self];
    [presentation loadUI];
  }
}

/*
 * Notification methods
 */

- (void) windowWillClose: (NSNotification *) aNotification
{
  /* the window may be modal. */
  if ([NSApp modalWindow] == owner) {
    [NSApp stopModal];
  }
  [currentPane willUnselect];
  [currentPane didUnselect];
}

/*
 * Action methods
 */

/** <p>Switches the current preference pane viewed to another one provided by
    <var>sender</var>.</p> */
- (IBAction) switchPaneView: (id)sender
{
  // NOTE: It could be better to have a method like 
  // -preferencePaneIdentifierForSender: on presentation builder side than
  // propagating the action method.
  [presentation switchPaneView: sender];
}

@end