/* 
   NSToolbarItem.m

   The Toolbar item class.
   
   Copyright (C) 2002 Free Software Foundation, Inc.

   Author:  Gregory John Casamento <greg_casamento@yahoo.com>,
            Fabien Vallon <fabien.vallon@fr.alcove.com>,
            Quentin Mathe <qmathe@club-internet.fr>
   Date: May 2002
   
   This file is part of the GNUstep GUI Library.

   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 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; see the file COPYING.LIB.
   If not, see <http://www.gnu.org/licenses/> or write to the 
   Free Software Foundation, 51 Franklin Street, Fifth Floor, 
   Boston, MA 02110-1301, USA.
*/ 

#import <Foundation/NSObject.h>
#import <Foundation/NSArray.h>
#import <Foundation/NSArchiver.h>
#import <Foundation/NSDictionary.h>
#import <Foundation/NSString.h>
#import "AppKit/NSApplication.h"
#import "AppKit/NSAttributedString.h"
#import "AppKit/NSButton.h"
#import "AppKit/NSButtonCell.h"
#import "AppKit/NSDragging.h"
#import "AppKit/NSEvent.h"
#import "AppKit/NSFont.h"
#import "AppKit/NSImage.h"
#import "AppKit/NSMenu.h"
#import "AppKit/NSMenuItem.h"
#import "AppKit/NSParagraphStyle.h"
#import "AppKit/NSPasteboard.h"
#import "AppKit/NSStringDrawing.h"
#import "AppKit/NSToolbar.h"
#import "AppKit/NSView.h"
#import "GNUstepGUI/GSToolbarView.h"
#import "AppKit/NSToolbarItem.h"

#import "NSToolbarFrameworkPrivate.h"
#import "GSGuiPrivate.h"

/*
 * Each NSToolbarItem object are coupled with a backView which is their 
 * representation on the screen.
 * backView for the standard toolbar item (without custom view) are NSButton 
 * subclass called GSToolbarButton.
 * backView for the toolbar item with a custom view are NSView subclass called
 * GSToolbarBackView.
 * GSToolbarButton and GSToolbarBackView are adjusted according to their content
 * and their title when the method layout is called.
 * The predefined GNUstep toolbar items are implemented with a class cluster 
 * pattern: initWithToolbarItemIdentifier: returns differents concrete subclass 
 * in accordance with the item identifier.
 */
 
typedef enum {
  ItemBackViewDefaultHeight = 60,
  ItemBackViewRegularHeight = 60,
  ItemBackViewSmallHeight = 50
} ItemBackViewHeight;

typedef enum {
  ItemBackViewDefaultWidth = 60,
  ItemBackViewRegularWidth = 60,
  ItemBackViewSmallWidth = 50
} ItemBackViewWidth;

static const int ItemBackViewX = 0;
static const int ItemBackViewY = 0;
static const int InsetItemViewX = 10;
static const int InsetItemViewY = 26;
static const int InsetItemTextX = 3;
static const int InsetItemTextY = 4;
 
// See NSToolbarItem -initialize method
static NSFont *NormalFont = nil;
static NSFont *SmallFont = nil;

NSString *GSMovableToolbarItemPboardType = @"GSMovableToolbarItemPboardType";

/*
 * NSButton subclass is the toolbar buttons _backView
 */
@interface GSToolbarButton : NSButton
{
  NSToolbarItem *_toolbarItem;
}

- (id) initWithToolbarItem: (NSToolbarItem *)toolbarItem;
- (void) layout;

// Accessors
- (NSToolbarItem *) toolbarItem;
@end

@interface GSToolbarButtonCell : NSButtonCell
{
  NSRect titleRect;
  NSRect imageRect;
}

@end

// ---

@implementation GSToolbarButton

+ (Class) cellClass
{
  return [GSToolbarButtonCell class];
}  

- (id) initWithToolbarItem: (NSToolbarItem *)toolbarItem
{ 
  // Frame will be reset by the layout method
  self = [super initWithFrame: NSMakeRect(ItemBackViewX, ItemBackViewY,
    ItemBackViewDefaultWidth, ItemBackViewDefaultHeight)]; 
  
  if (self != nil)
    {
      // Don't do an ASSIGN here, the toolbar item itself retains us.
      _toolbarItem = toolbarItem;

      [self setTitle: @""];
      [self setEnabled: NO];
      [_cell setBezeled: YES];
      [self setImagePosition: NSImageAbove];
      [self setHighlightsBy: 
                NSChangeGrayCellMask | NSChangeBackgroundCellMask];
      [self setFont: NormalFont]; 
    }

  return self;   
}

/*
 * The code below should be kept in sync with GSToolbarBackView methods which
 * have identical names.
 */
 
- (void) layout
{
  float textWidth, layoutedWidth = -1, layoutedHeight = -1;
  NSFont *font;
  unsigned int borderMask = [[[_toolbarItem toolbar] _toolbarView] borderMask];
  NSSize labelSize = NSZeroSize;

  font = NormalFont;
  
  // Adjust the layout in accordance with NSToolbarSizeMode
  switch ([[_toolbarItem toolbar] sizeMode])
    {
      case NSToolbarSizeModeDefault:
        layoutedWidth = ItemBackViewDefaultWidth;
        layoutedHeight = ItemBackViewDefaultHeight;
        [[_toolbarItem image] setSize: NSMakeSize(32, 32)];
        break;
      case NSToolbarSizeModeRegular:
        layoutedWidth = ItemBackViewRegularWidth;
        layoutedHeight = ItemBackViewRegularHeight;
        [[_toolbarItem image] setSize: NSMakeSize(32, 32)];
        break;
      case NSToolbarSizeModeSmall:
        layoutedWidth = ItemBackViewSmallWidth;
        layoutedHeight = ItemBackViewSmallHeight;
        /* Not use [self image] here because it can return nil, when image 
           position is set to NSNoImage. Even if NSToolbarDisplayModeTextOnly 
           is not true anymore -setImagePosition: is only called below, then 
           [self image] can still returns nil. */
        [[_toolbarItem image] setSize: NSMakeSize(24, 24)];
        font = SmallFont;
        break;
      default:
        NSLog(@"Invalid NSToolbarSizeMode"); // Invalid
    }
    
  [self setFont: font];
  
  // Adjust the layout in accordance with the border
  if (!(borderMask & GSToolbarViewBottomBorder))
    {
      layoutedHeight++;
      layoutedWidth++;
    }

  if (!(borderMask & GSToolbarViewTopBorder))
    {
      layoutedHeight++;
      layoutedWidth++; 
    }
             
  // Adjust the layout in accordance with the label
  {
    NSAttributedString *attrStr;
    NSDictionary *attr;
    NSString *label = [_toolbarItem label];

    attr = [NSDictionary dictionaryWithObject: font forKey: NSFontAttributeName];
    if (label == nil || [label isEqualToString: @""])
      label = @"Dummy";
    attrStr = [[NSAttributedString alloc] initWithString: label attributes: attr];
    labelSize = [attrStr size];
    DESTROY(attrStr);
  }
      
  textWidth = labelSize.width + 2 * InsetItemTextX;
  if ([[_toolbarItem toolbar] displayMode] != NSToolbarDisplayModeIconOnly 
    && layoutedWidth != -1 && textWidth > layoutedWidth) 
     layoutedWidth = textWidth;
     
  // Adjust the layout in accordance with NSToolbarDisplayMode
  switch ([[_toolbarItem toolbar] displayMode])
    {
      case NSToolbarDisplayModeDefault:
        [self setImagePosition: NSImageAbove];
        break;
      case NSToolbarDisplayModeIconAndLabel:
        [self setImagePosition: NSImageAbove];
        break;
      case NSToolbarDisplayModeIconOnly:
        [self setImagePosition: NSImageOnly];
        layoutedHeight -= labelSize.height + InsetItemTextY;
        break;
      case NSToolbarDisplayModeLabelOnly:
        [self setImagePosition: NSNoImage];
        layoutedHeight = labelSize.height + InsetItemTextY * 2;
        break;
      default:
        ; // Invalid
    }
      
  // Set the frame size to use the new layout
  [self setFrameSize: NSMakeSize(layoutedWidth, layoutedHeight)];
}

- (void) mouseDown: (NSEvent *)event
{
  NSToolbar *toolbar = [_toolbarItem toolbar];
  
  if (([event modifierFlags] == NSCommandKeyMask 
       && [toolbar allowsUserCustomization])
      || [toolbar customizationPaletteIsRunning] || toolbar == nil)
    {          
      NSSize viewSize = [self frame].size;
      NSImage *image = [[NSImage alloc] initWithSize: viewSize];
      NSCell *cell = [self cell];
      NSPasteboard *pboard;
      int index = NSNotFound;
          
      // Prepare the drag
      
      /* We need to keep this view (aka self) to be able to draw the drag
         image. */
      RETAIN(self); 
      
      // Draw the drag content in an image
      /* The code below is only partially supported by GNUstep, then NSImage
         needs to be improved. */
      [image lockFocus];
      [cell setShowsFirstResponder: NO]; // To remove the dotted rect
      [cell drawWithFrame: 
                NSMakeRect(0, 0, viewSize.width, viewSize.height) inView: nil];
      [cell setShowsFirstResponder: YES];
      [image unlockFocus];
      
      pboard = [NSPasteboard pasteboardWithName: NSDragPboard];
      [pboard declareTypes: [NSArray arrayWithObject: GSMovableToolbarItemPboardType] 
              owner: nil];
      if (toolbar != nil)
	{
          index = [toolbar _indexOfItem: _toolbarItem];
        }
	  [GSToolbarView setDraggedItemIndex:index];
      [pboard setString: [NSString stringWithFormat:@"%d", index] 
              forType: GSMovableToolbarItemPboardType];
          
      [self dragImage: image 
            at: NSMakePoint(0.0, viewSize.height)
            offset: NSMakeSize(0.0, 0.0)
            event: event 
            pasteboard: pboard 
            source: self
            slideBack: NO];          
      RELEASE(image);
    }
  else if ([event modifierFlags] != NSCommandKeyMask)
    {
      [super mouseDown: event];
    }
}

- (void) draggedImage: (NSImage *)dragImage beganAt: (NSPoint)location
{
  //nothing to do
}

- (void) draggedImage: (NSImage *)dragImage 
              endedAt: (NSPoint)location 
            operation: (NSDragOperation)operation
{
  //nothing to do
}

- (NSDragOperation) draggingSourceOperationMaskForLocal: (BOOL)isLocal
{
  return isLocal ? NSDragOperationGeneric : NSDragOperationNone;
}

- (NSToolbarItem *) toolbarItem
{
  return _toolbarItem;
}

/*
 * End of the code to keep in sync
 */

- (BOOL) sendAction: (SEL)action to: (id)target
{ 
  if ([_toolbarItem _selectable])
    {
      [[_toolbarItem toolbar] 
        setSelectedItemIdentifier: [_toolbarItem itemIdentifier]];
    }
  
  if (action)
    {
      // Send from toolbar item not self
      return [NSApp sendAction: action 
                            to: target 
                          from: _toolbarItem];
    }
  else
    {
      return NO;
    }
}

@end

@implementation GSToolbarButtonCell

/* Overriden NSButtonCell method to handle cell type in a basic way which avoids
   to lose image or empty title on new image position (when this involves a cell
   type switch) and the need to reset it. That would happen in GSToolbarButton
   -layout method (on toolbar display mode switch).
   Note that empty title are used with space or separator toolbar items. */
- (void) setImagePosition: (NSCellImagePosition)aPosition
{
  _cell.image_position = aPosition;

  if (_cell.image_position == NSNoImage)
    {
      _cell.type = NSTextCellType;
    }
  else
    {
      _cell.type = NSImageCellType;
    }
}

/* Allways return the image, even when no image gets displayed. */
- (NSImage*) image
{
  return _cell_image;
}

// Overriden NSButtonCell method to make sure all text is at the same height.
- (void) drawInteriorWithFrame: (NSRect)cellFrame inView: (NSView*)controlView
{
  BOOL flippedView = [controlView isFlipped];
  NSCellImagePosition ipos = _cell.image_position;
  // We ignore alternateAttributedTitle, it is not needed
  NSSize titleSize = [[self attributedTitle] size];
  
  if (flippedView == YES)
    {
      if (ipos == NSImageAbove)
        {
          ipos = NSImageBelow;
        }
      else if (ipos == NSImageBelow)
        {
          ipos = NSImageAbove;
        }
    }

  /* We store the values we need to customize the drawing into titleRect and 
     imageRect. */
  switch (ipos)
    {
      case NSNoImage: 
        titleRect = cellFrame;
        break;

      case NSImageOnly: 
        imageRect = cellFrame;
        break;

      default:
      case NSImageBelow: 
        titleRect.origin.x = cellFrame.origin.x;
        titleRect.origin.y = NSMaxY(cellFrame) - titleSize.height - InsetItemTextY;
        titleRect.size.width = cellFrame.size.width;
        titleRect.size.height = titleSize.height;
        
        imageRect.origin.x = cellFrame.origin.x;
        imageRect.origin.y = cellFrame.origin.y;
        imageRect.size.width = cellFrame.size.width;
        imageRect.size.height = cellFrame.size.height - titleRect.size.height;
        break;

      case NSImageAbove: 
        titleRect.origin.x = cellFrame.origin.x;
        titleRect.origin.y = cellFrame.origin.y + InsetItemTextY;
        titleRect.size.width = cellFrame.size.width;
        titleRect.size.height = titleSize.height;
        
        imageRect.origin.x = cellFrame.origin.x;
        imageRect.origin.y = cellFrame.origin.y + titleRect.size.height;
        imageRect.size.width = cellFrame.size.width;
        imageRect.size.height = cellFrame.size.height - titleRect.size.height;
        break;
    }

  [super drawInteriorWithFrame: cellFrame inView: controlView];
}

// Overriden NSCell method
- (void) _drawAttributedText: (NSAttributedString*)aString 
                     inFrame: (NSRect)aRect
{
  if (aString == nil)
    return;

  /* Important: text should always be vertically centered without considering 
     descender (as if descender did not exist). This is particularly important 
     for single line texts.Please make sure the output remains always 
     correct. */

  [aString drawInRect: titleRect]; // We ignore aRect value
}

// Overriden NSButtonCell method
- (void) drawImage: (NSImage *)anImage withFrame: (NSRect)aRect inView: (NSView*)controlView
{
  // We ignore aRect value
  [super drawImage: anImage withFrame: imageRect inView: controlView];
}

@end

/*
 * Back view used to enclose toolbar item's custom view
 */
@interface GSToolbarBackView : NSView
{
  NSToolbarItem *_toolbarItem;
  NSFont *_font;
  BOOL _enabled;
  BOOL _showLabel;
  // record the fact that the view responds to these
  // to save time.
  struct __flags
  {
    // gets
    unsigned int _isEnabled:1;
    unsigned int _action:1;
    unsigned int _target:1;
    unsigned int _image:1;
    // sets
    unsigned int _setEnabled:1;
    unsigned int _setAction:1;
    unsigned int _setTarget:1;
    unsigned int _setImage:1;

    // to even out the int.
    unsigned int RESERVED:24;
  } _flags;
}

- (id) initWithToolbarItem: (NSToolbarItem *)toolbarItem;
- (void) layout;

- (NSToolbarItem *) toolbarItem;
- (BOOL) isEnabled;
- (void) setEnabled: (BOOL)enabled;
@end

@implementation GSToolbarBackView

- (id) initWithToolbarItem: (NSToolbarItem *)toolbarItem
{  
  self = [super initWithFrame: NSMakeRect(ItemBackViewX, ItemBackViewY, 
    ItemBackViewDefaultWidth, ItemBackViewDefaultHeight)];
  // Frame will be reset by the layout method
  
  if (self != nil)
    {  
      NSView *view;

      // Don't do an ASSIGN here, the toolbar item itself retains us.
      _toolbarItem = toolbarItem;
      view = [toolbarItem view];

      // gets
      _flags._isEnabled  = [view respondsToSelector: @selector(isEnabled)];
      _flags._action     = [view respondsToSelector: @selector(action)];
      _flags._target     = [view respondsToSelector: @selector(target)];
      _flags._image      = [view respondsToSelector: @selector(image)];
      // sets
      _flags._setEnabled = [view respondsToSelector: @selector(setEnabled:)];
      _flags._setAction  = [view respondsToSelector: @selector(setAction:)];
      _flags._setTarget  = [view respondsToSelector: @selector(setTarget:)];
      _flags._setImage   = [view respondsToSelector: @selector(setImage:)];
    }
  
  return self;
}

- (void) drawRect: (NSRect)rect
{  
  if (_showLabel)
    {
      NSAttributedString *attrString;
      NSDictionary *attr;
      NSColor *color;
      NSMutableParagraphStyle *pStyle;
      NSRect titleRect;
      NSRect viewBounds = [self bounds];

      if (_enabled)
        {
          color = [NSColor blackColor];
        }
      else
        {
          color = [NSColor disabledControlTextColor];
        }
        
      pStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
      [pStyle setAlignment: NSCenterTextAlignment];
      
      // We draw the label
      attr = [NSDictionary dictionaryWithObjectsAndKeys: _font, 
        NSFontAttributeName, color, NSForegroundColorAttributeName, pStyle,
        NSParagraphStyleAttributeName, nil];
      RELEASE(pStyle);

      attrString = [[NSAttributedString alloc] 
        initWithString: [_toolbarItem label] attributes: attr];
      
      titleRect.origin.x = viewBounds.origin.x;
      titleRect.origin.y = viewBounds.origin.y + InsetItemTextY;
      titleRect.size.width = viewBounds.size.width;
      titleRect.size.height = [attrString size].height;
      [attrString drawInRect: titleRect];
      
      DESTROY(attrString);
   }
}

- (NSView *) hitTest: (NSPoint)point
{
  if (_super_view && [_super_view mouse: point inRect: _frame])
    {
      NSEvent *event = [NSApp currentEvent];
      NSToolbar *toolbar = [_toolbarItem toolbar];

      if (([event type] == NSLeftMouseDown)
	  && (([event modifierFlags] == NSCommandKeyMask
	      && [toolbar allowsUserCustomization])
	      || [toolbar customizationPaletteIsRunning] || toolbar == nil))
	{
	  return self;
	}
    }

  return [super hitTest: point];
}

/*
 * The code below should be kept in sync with GSToolbarButton methods which 
 * have identical names.
 */
  
- (void) layout
{
  NSView *view = [_toolbarItem view];
  float textWidth, layoutedWidth = -1, layoutedHeight = -1;
  NSFont *font;
  unsigned int borderMask = [[[_toolbarItem toolbar] _toolbarView] borderMask];
  NSSize labelSize = NSZeroSize;
  
  font = NormalFont;
  
  if ([view superview] == nil) // Show the view to eventually hide it later
    [self addSubview: view];
	
  if ([view respondsToSelector: @selector(sizeToFit)])
    {
      NSSize newSize, minSize = [_toolbarItem minSize];
      
      [(id)view sizeToFit];
      newSize = [view frame].size;
      if (minSize.width > 0 || newSize.height < minSize.height)
        {
          if (minSize.width > 0)
			newSize.width = minSize.width;
          newSize.height = MAX(newSize.height, minSize.height);
          [view setFrameSize: newSize];
        }
    }

  // Adjust the layout in accordance with NSToolbarSizeMode
  switch ([[_toolbarItem toolbar] sizeMode])
    {
      case NSToolbarSizeModeDefault:
        layoutedWidth = ItemBackViewDefaultWidth;
        layoutedHeight = ItemBackViewDefaultHeight;
        if ([view frame].size.height > 32)
          [view removeFromSuperview];
        break;
      case NSToolbarSizeModeRegular:
        layoutedWidth = ItemBackViewRegularWidth;
        layoutedHeight = ItemBackViewRegularHeight;
        if ([view frame].size.height > 32)
          [view removeFromSuperview];
        break;
      case NSToolbarSizeModeSmall:
        layoutedWidth = ItemBackViewSmallWidth;
        layoutedHeight = ItemBackViewSmallHeight;
        font = SmallFont;
        if ([view frame].size.height > 24)
          [view removeFromSuperview];
        break;
      default:
        NSLog(@"Invalid NSToolbarSizeMode"); // Invalid
    } 
    
  _font = font;

  // Adjust the layout in accordance with the border
  if (!(borderMask & GSToolbarViewBottomBorder))
    {
      layoutedHeight++;
      layoutedWidth++;
    }

  if (!(borderMask & GSToolbarViewTopBorder))
    {
      layoutedHeight++;
      layoutedWidth++; 
    }
  
  // Adjust the layout in accordance with the label
  {
    NSAttributedString *attrStr;
    NSDictionary *attr;
    NSString *label = [_toolbarItem label];

    attr = [NSDictionary dictionaryWithObject: font forKey: NSFontAttributeName];
    if (label == nil || [label isEqualToString: @""])
      label = @"Dummy";
    attrStr = [[NSAttributedString alloc] initWithString: label attributes: attr];
    labelSize = [attrStr size];
    DESTROY(attrStr);
  }
   
  textWidth = labelSize.width + 2 * InsetItemTextX;
  if (textWidth > layoutedWidth)
    layoutedWidth = textWidth;
  
  _enabled = YES;
  /* This boolean variable is used to known when it's needed to draw the label
     in the -drawRect: method. */
  _showLabel = YES; 
   
  // Adjust the layout in accordance with NSToolbarDisplayMode
  switch ([[_toolbarItem toolbar] displayMode])
    {
      case NSToolbarDisplayModeDefault:
        break; // Nothing to do
      case NSToolbarDisplayModeIconAndLabel:
        break; // Nothing to do
      case NSToolbarDisplayModeIconOnly:
        _showLabel = NO;
        layoutedHeight -= labelSize.height + InsetItemTextY;
        break;
      case NSToolbarDisplayModeLabelOnly:
        _enabled = NO;
        layoutedHeight = labelSize.height + InsetItemTextY * 2;
        if ([view superview] != nil)
          [view removeFromSuperview];
        break;
      default:
        ; // Invalid
    }
   
  /* If the view is visible... 
     Adjust the layout in accordance with the view width in the case it is 
     needed. */
  if ([view superview] != nil)
    { 
      if (layoutedWidth < [view frame].size.width + 2 * InsetItemViewX)
        layoutedWidth = [view frame].size.width + 2 * InsetItemViewX; 
    }
  
  // Set the frame size to use the new layout
  [self setFrameSize: NSMakeSize(layoutedWidth, layoutedHeight)];
  
  /* If the view is visible...
     Adjust the view position in accordance with the new layout. */
  if ([view superview] != nil)
    {
      float insetItemViewY = ([self frame].size.height 
			      - [view frame].size.height) / 2;

      if (_showLabel)
        {
          insetItemViewY += (labelSize.height + InsetItemTextY) / 2;
        }
        
      [view setFrameOrigin: NSMakePoint((layoutedWidth 
        - [view frame].size.width) / 2, insetItemViewY)];
    }    
}

- (void) mouseDown: (NSEvent *)event
{
  NSToolbar *toolbar = [_toolbarItem toolbar];
  
  if (([event modifierFlags] == NSCommandKeyMask 
       && [toolbar allowsUserCustomization])
      || [toolbar customizationPaletteIsRunning] || toolbar == nil)
    {          
      NSSize viewSize = [self frame].size;
      NSImage *image = [[NSImage alloc] initWithSize: viewSize];
      NSPasteboard *pboard;
      int index = NSNotFound;
      
      // Prepare the drag
      
      /* We need to keep this view (aka self) to be able to draw the drag
         image. */
      RETAIN(self); 
      
      // Draw the drag content in an image
      /* The code below is only partially supported by GNUstep, then NSImage
         needs to be improved. */
      [image lockFocus];
      [self drawRect: 
                NSMakeRect(0, 0, viewSize.width, viewSize.height)];
      [image unlockFocus];
      
      pboard = [NSPasteboard pasteboardWithName: NSDragPboard];
      [pboard declareTypes: [NSArray arrayWithObject: GSMovableToolbarItemPboardType] 
              owner: nil];
      if (toolbar != nil)
	{
          index = [toolbar _indexOfItem: _toolbarItem];
        }
	  [GSToolbarView setDraggedItemIndex:index];
      [pboard setString: [NSString stringWithFormat:@"%d", index] 
              forType: GSMovableToolbarItemPboardType];
      
      [self dragImage: image 
            //at: NSMakePoint(0.0, viewSize.height)
            at: NSMakePoint(0.0, 0.0)
            offset: NSMakeSize(0.0, 0.0)
            event: event 
            pasteboard: pboard 
            source: self
            slideBack: NO];
      RELEASE(image);
    }
  else if ([event modifierFlags] != NSCommandKeyMask)
    {
      [super mouseDown: event];
    }
}

- (void) draggedImage: (NSImage *)dragImage beganAt: (NSPoint)location
{
  //nothing to do
}

- (void) draggedImage: (NSImage *)dragImage 
              endedAt: (NSPoint)location 
            operation: (NSDragOperation)operation
{
  //nothing to do
}

- (NSDragOperation) draggingSourceOperationMaskForLocal: (BOOL)isLocal
{
  return isLocal ? NSDragOperationGeneric : NSDragOperationNone;
}

- (NSToolbarItem *) toolbarItem
{
  return _toolbarItem;
}

/*
 * End of the code to keep in sync
 */

- (BOOL) isEnabled
{
  if (_flags._isEnabled)
    {
      id view = [_toolbarItem view];
  
      return [view isEnabled];
    }
  else
    {
      return _enabled;
    }
}

- (void) setEnabled: (BOOL)enabled
{
  if (_flags._setEnabled)
    {
      id view = [_toolbarItem view];

      [view setEnabled: enabled];
    }
  else 
    {
      _enabled = enabled;
    }
}

- (NSImage *) image
{
  if (_flags._image)
    {
      id view = [_toolbarItem view];

      return [view image];
    }
  else
    {
      return nil;
    }
}

- (void) setImage: (NSImage *)image
{
  if (_flags._setImage)
    {
      id view = [_toolbarItem view];

      [view setImage: image];
    }
}

- (void) setAction: (SEL)action
{
  if (_flags._setAction)
    {
      id view = [_toolbarItem view];

      [view setAction: action];
    }
}

- (SEL) action
{
  if (_flags._action)
    {
      id view = [_toolbarItem view];

      return [view action];
    }
  else
    {
      return 0;
    }
}

- (void) setTarget: (id)target
{
  if (_flags._setTarget)
    {
      id view = [_toolbarItem view];

      [view setTarget: target];
    }
}

- (id) target
{
  if (_flags._target)
    {
      id view = [_toolbarItem view];

      return [view target];
    }
  else
    {
      return nil;
    }
}

@end

/*
 * Standard toolbar items.
 */

// ---- NSToolbarSeparatorItemIdentifier
@interface GSToolbarSeparatorItem : NSToolbarItem
{
}
@end

@implementation GSToolbarSeparatorItem
- (id) initWithItemIdentifier: (NSString *)itemIdentifier
{
  self = [super initWithItemIdentifier: itemIdentifier];
  if (!self)
    return nil;

  [(NSButton *)[self _backView] setImagePosition: NSImageOnly];
  [(NSButton *)[self _backView] setImage: 
                   [NSImage imageNamed: @"common_ToolbarSeparatorItem"]];
  /* We bypass the toolbar item accessor to set the image in order to have it
     (48 * 48) not resized. */
   [self setPaletteLabel: _(@"Separator")];
  
  [[self _backView] setFrameSize: NSMakeSize(30, ItemBackViewDefaultHeight)];
  
  return self;
}

- (NSMenuItem *) _defaultMenuFormRepresentation 
{
  return nil; // Override the default implementation in order to do nothing
}

- (void) _layout 
{
  NSView *backView = [self _backView];
  
  // Override the default implementation
  
  [(id)backView layout];
  if ([self toolbar] != nil)
    [backView setFrameSize: NSMakeSize(30, [backView frame].size.height)];
}

- (BOOL) allowsDuplicatesInToolbar
{
  return YES;
}

@end

// ---- NSToolbarSpaceItemIdentifier
@interface GSToolbarSpaceItem : NSToolbarItem
{
}
@end

@implementation GSToolbarSpaceItem
- (id) initWithItemIdentifier: (NSString *)itemIdentifier
{ 
  self = [super initWithItemIdentifier: itemIdentifier];
  if (!self)
    return nil;
  [self setPaletteLabel: _(@"Space")];
   
  return self;
}

// Override the default implementation in order to do nothing
- (NSMenuItem *) _defaultMenuFormRepresentation 
{
  return nil;
}

- (BOOL) allowsDuplicatesInToolbar
{
  return YES;
}

@end

// ---- NSToolbarFlexibleSpaceItemIdentifier
@interface GSToolbarFlexibleSpaceItem : NSToolbarItem
{
}
@end

@implementation GSToolbarFlexibleSpaceItem
- (id) initWithItemIdentifier: (NSString *)itemIdentifier
{
  self = [super initWithItemIdentifier: itemIdentifier];
  if (!self)
    return nil;
  [self setPaletteLabel: _(@"Flexible Space")];
  [self _layout];
  
  return self;
}

// Override the default implementation in order to do nothing
- (NSMenuItem *) _defaultMenuFormRepresentation 
{
  return nil;
}

// Override the default implementation in order to reset the _backView to a zero width
- (void) _layout 
{
  NSView *backView = [self _backView];
  NSSize size;

  [(id)backView layout];
  size = [backView frame].size;
  
  /* If the item is not part of a toolbar, this usually means it is used by
     customization palette, we shouldn't resize it in this case. */
  if ([self toolbar] != nil)
    [backView setFrameSize: NSMakeSize(0, size.height)];
  
  [self setMinSize: NSMakeSize(0, size.height)];
  [self setMaxSize: NSMakeSize(10000, size.height)];
}

- (BOOL) _isFlexibleSpace
{
  return YES;
}

- (BOOL) allowsDuplicatesInToolbar
{
  return YES;
}

@end

// ---- NSToolbarShowColorsItemIdentifier
@interface GSToolbarShowColorsItem : NSToolbarItem
{
}
@end

@implementation GSToolbarShowColorsItem
- (id) initWithItemIdentifier: (NSString *)itemIdentifier
{
  self = [super initWithItemIdentifier: itemIdentifier];
  if (!self)
    return nil;
  [self setImage: [NSImage imageNamed: @"common_ToolbarShowColorsItem"]];
  [self setLabel: _(@"Colors")];

  // Set action...
  [self setTarget: nil]; // Goes to first responder..
  [self setAction: @selector(orderFrontColorPanel:)];

  return self;
}
@end

// ---- NSToolbarShowFontsItemIdentifier
@interface GSToolbarShowFontsItem : NSToolbarItem
{
}
@end

@implementation GSToolbarShowFontsItem
- (id) initWithItemIdentifier: (NSString *)itemIdentifier
{
  self = [super initWithItemIdentifier: itemIdentifier];
  if (!self)
    return nil;
  [self setImage: [NSImage imageNamed: @"common_ToolbarShowFontsItem"]];
  [self setLabel: _(@"Fonts")];

  // Set action...
  [self setTarget: nil]; // Goes to first responder..
  [self setAction: @selector(orderFrontFontPanel:)];

  return self;
}
@end

// ---- NSToolbarCustomizeToolbarItemIdentifier
@interface GSToolbarCustomizeToolbarItem : NSToolbarItem
{
}
@end

@implementation GSToolbarCustomizeToolbarItem
- (id) initWithItemIdentifier: (NSString *)itemIdentifier
{
  self = [super initWithItemIdentifier: itemIdentifier];
  if (!self)
    return nil;
  [self setImage: [NSImage imageNamed: @"common_ToolbarCustomizeToolbarItem"]];
  [self setLabel: _(@"Customize")];

  // Set action...
  [self setTarget: nil]; // Goes to first responder..
  [self setAction: @selector(runToolbarCustomizationPalette:)];

  return self;
}
@end

// ---- NSToolbarPrintItemIdentifier
@interface GSToolbarPrintItem : NSToolbarItem
{
}
@end

@implementation GSToolbarPrintItem
- (id) initWithItemIdentifier: (NSString *)itemIdentifier
{
  self = [super initWithItemIdentifier: itemIdentifier];
  if (!self)
    return nil;
  [self setImage: [NSImage imageNamed: @"common_Printer"]];
  [self setLabel: _(@"Print...")];

  // Set action...
  [self setTarget: nil]; // goes to first responder..
  [self setAction: @selector(printDocument:)];

  return self;
}
@end


@implementation NSToolbarItem

+ (void) initialize
{
  // This used to be size 11.
  NormalFont = RETAIN([NSFont systemFontOfSize: [NSFont systemFontSize]]);
  // [NSFont smallSystemFontSize] or better should be NSControlContentFontSize
  SmallFont = RETAIN([NSFont systemFontOfSize: [NSFont smallSystemFontSize]]);
}

- (id) initWithItemIdentifier: (NSString *)itemIdentifier
{
  // GNUstep predefined toolbar items
  if ([itemIdentifier isEqualToString: NSToolbarSeparatorItemIdentifier] 
      && [self isKindOfClass: [GSToolbarSeparatorItem class]] == NO)
    {
      RELEASE(self);
      return [[GSToolbarSeparatorItem alloc] 
                 initWithItemIdentifier: itemIdentifier];
    }
  else if ([itemIdentifier isEqualToString: NSToolbarSpaceItemIdentifier]
           && [self isKindOfClass: [GSToolbarSpaceItem class]] == NO)
    {
      RELEASE(self);
      return [[GSToolbarSpaceItem alloc] 
                 initWithItemIdentifier: itemIdentifier];
    }
  else if ([itemIdentifier 
               isEqualToString: NSToolbarFlexibleSpaceItemIdentifier] 
           && [self isKindOfClass: [GSToolbarFlexibleSpaceItem class]] == NO)
    {
      RELEASE(self);
      return [[GSToolbarFlexibleSpaceItem alloc] 
                 initWithItemIdentifier: itemIdentifier];
    }
  else if ([itemIdentifier 
               isEqualToString: NSToolbarShowColorsItemIdentifier]
           && [self isKindOfClass: [GSToolbarShowColorsItem class]] == NO)
    {
      RELEASE(self);
      return [[GSToolbarShowColorsItem alloc] 
                 initWithItemIdentifier: itemIdentifier];
    }
  else if ([itemIdentifier 
               isEqualToString: NSToolbarShowFontsItemIdentifier]
           && [self isKindOfClass: [GSToolbarShowFontsItem class]] == NO)
    {
      RELEASE(self);
      return [[GSToolbarShowFontsItem alloc] 
                 initWithItemIdentifier: itemIdentifier];
    }
  else if ([itemIdentifier 
               isEqualToString: NSToolbarCustomizeToolbarItemIdentifier]
           && [self isKindOfClass: [GSToolbarCustomizeToolbarItem class]] == NO)
    {
      RELEASE(self);
      return [[GSToolbarCustomizeToolbarItem alloc] 
                 initWithItemIdentifier: itemIdentifier];
    }
  else if ([itemIdentifier isEqualToString: NSToolbarPrintItemIdentifier]
           && [self isKindOfClass: [GSToolbarPrintItem class]] == NO)
    {
      RELEASE(self);
      return [[GSToolbarPrintItem alloc] 
                 initWithItemIdentifier: itemIdentifier];
    }
  else
    {
      if ((self = [super init]) != nil)
        {
          // Normal toolbar items
          ASSIGN(_itemIdentifier, itemIdentifier);
          [self setAutovalidates: YES];

          // Set the backview to an GSToolbarButton, will get reset to a 
          // GSToolbarBackView when setView: gets called.
	  [self setView: nil];
        }        
    }
  
  return self;
}

- (void) dealloc
{
  RELEASE(_itemIdentifier);
  RELEASE(_label);
  RELEASE(_image);
  RELEASE(_menuFormRepresentation);
  RELEASE(_paletteLabel);
  RELEASE(_toolTip);
  TEST_RELEASE(_view);
  RELEASE(_backView);
  
  [super dealloc];
}

- (BOOL) allowsDuplicatesInToolbar
{
  return NO;
}

- (BOOL) isEnabled
{
  return [(GSToolbarButton*)_backView isEnabled];
}

- (NSImage *) image
{
  // return [(GSToolbarButton*)_backView image];
  return _image;
}

- (NSString *) itemIdentifier
{
  return _itemIdentifier;
}

- (NSString *) label
{
  // FIXME: I think this is not needed
  if ([[self toolbar] displayMode] == NSToolbarDisplayModeLabelOnly)
    {
      NSMenuItem *menuItem = [self menuFormRepresentation];
      
      if (menuItem != nil)
        return [menuItem title];
    }

  return _label;
}

- (NSSize) maxSize
{
  return _maxSize;
}

- (NSMenuItem *) menuFormRepresentation
{
  return _menuFormRepresentation;
}

- (NSSize) minSize
{
  return _minSize;
}

- (NSString *) paletteLabel
{
  return _paletteLabel;
}

- (void) setAction: (SEL)action
{
  [(GSToolbarButton *)_backView setAction: action];
        
  [self setEnabled: (action != NULL)];
}

- (void) setEnabled: (BOOL)enabled
{
  [(GSToolbarButton*)_backView setEnabled: enabled];
}

- (void) setImage: (NSImage *)image
{
  ASSIGN(_image, image);  
      
  [_image setScalesWhenResized: YES];
  //[_image setSize: NSMakeSize(32, 32)];

  [(GSToolbarButton*)_backView setImage: image];
}

- (void) setLabel: (NSString *)label
{
  ASSIGN(_label, label);
  
  if (!_view)
    [(GSToolbarButton *)_backView setTitle: _label];

  _modified = YES;
  if (_toolbar != nil)
    [[_toolbar _toolbarView] _reload];
}

- (void) setMaxSize: (NSSize)maxSize
{
  _maxSize = maxSize;
}

- (void) setMenuFormRepresentation: (NSMenuItem *)menuItem
{
  ASSIGN(_menuFormRepresentation, menuItem);
}

- (void) setMinSize: (NSSize)minSize
{
  _minSize = minSize;
}

- (void) setPaletteLabel: (NSString *)paletteLabel
{
  ASSIGN(_paletteLabel, paletteLabel);
}

- (void) setTag: (NSInteger)tag
{
  _tag = tag;
}

- (void) setTarget: (id)target
{
  [(NSButton *)_backView setTarget: target];
}

- (void) setToolTip: (NSString *)toolTip
{
  ASSIGN(_toolTip, toolTip);
  [_view setToolTip: _toolTip];
}

- (void) setView: (NSView *)view
{
  if ((_view == view) && (_backView != nil))
    return;
    
  ASSIGN(_view, view);

  if (view)
    {
      NSSize size;

      size = [view frame].size;
      if (NSEqualSizes(NSZeroSize, _minSize))
          [self setMinSize: size];
      if (NSEqualSizes(NSZeroSize, _maxSize))
          [self setMaxSize: size];

      [_view setToolTip: _toolTip];

      RELEASE(_backView);
      _backView = [[GSToolbarBackView alloc] initWithToolbarItem: self];
    }
  else
    {
      RELEASE(_backView);
      _backView = [[GSToolbarButton alloc] initWithToolbarItem: self];
    }
}

- (NSInteger) tag
{
  return _tag;
}

- (NSString *) toolTip
{
  return _toolTip;
}

- (NSToolbar *) toolbar
{
  return _toolbar;
}

- (void) validate
{
  BOOL enabled = YES;
  id target;

  /* No validation for custom views */
  if (_view)
    return;

  target = [NSApp targetForAction: [self action] to: [self target] from: self];
  if (target == nil || ![target respondsToSelector: [self action]])
    {
      enabled = NO;
    }
  else if ([target respondsToSelector: @selector(validateToolbarItem:)])
    {
      enabled = [target validateToolbarItem: self];
    }
  else if ([target respondsToSelector: @selector(validateUserInterfaceItem:)])
    {
      enabled = [target validateUserInterfaceItem: self];
    }
  [self setEnabled: enabled];
}

- (NSView *) view
{
  return _view;
}

// Private or package like visibility methods

- (NSView *) _backView
{
  return _backView;
}

//
// This method invokes using the toolbar item as the sender.
// When invoking from the menu, it shouldn't send the menuitem as the
// sender since some applications check this and try to get additional
// information about the toolbar item which this is coming from. Since
// we implement the menu's action, we must also validate it.
//
- (void) _sendAction: (id)sender
{
  [NSApp sendAction: [self action] 
	 to: [self target]
	 from: self];
}

- (BOOL) validateMenuItem: (NSMenuItem *)menuItem
{
  return [self isEnabled];
}

- (NSMenuItem *) _defaultMenuFormRepresentation
{
  NSMenuItem *menuItem;
  
  menuItem = [[NSMenuItem alloc] initWithTitle: [self label]  
				 action: @selector(_sendAction:) 
                                 keyEquivalent: @""];
  [menuItem setTarget: self];
  AUTORELEASE(menuItem);
  
  return menuItem;
}

- (void) _layout
{
  [(id)_backView layout];
}

- (BOOL) _isModified
{
  return _modified;
}

- (BOOL) _isFlexibleSpace
{
  return NO;
}

- (BOOL) _selectable
{
  return _selectable;
}

- (BOOL) _selected
{
  return [(GSToolbarButton *)_backView state];
}

- (void) _setSelected: (BOOL)selected
{
  if (_selectable)
    {
      if ([self _selected] != selected)
        [(GSToolbarButton *)_backView setState: selected];
    }
  else
    {
      NSLog(@"The toolbar item %@ is not selectable", self);
    }
}

- (void) _setSelectable: (BOOL)selectable
{
  if ([_backView isKindOfClass: [GSToolbarButton class]])
    {
      _selectable = selectable;
      [(GSToolbarButton *)_backView setButtonType: NSOnOffButton];
    }
  else
    {
      NSLog(@"The toolbar item %@ is not selectable", self);
    }   
}

- (void) _setToolbar: (NSToolbar *)toolbar
{
  // Don't do an ASSIGN here, the toolbar itself retains us.
  _toolbar = toolbar;
}

- (BOOL) autovalidates
{
  return _autovalidates;
}

- (void) setAutovalidates: (BOOL)autovalidates
{
  _autovalidates = autovalidates;
}

- (NSInteger) visibilityPriority
{
  return _visibilityPriority;
}

- (void) setVisibilityPriority: (NSInteger)visibilityPriority
{
  _visibilityPriority = visibilityPriority;
}

// NSValidatedUserInterfaceItem protocol
- (SEL) action
{
  return [(GSToolbarButton *)_backView action];
}

- (id) target
{
  return [(GSToolbarButton *)_backView target];
}

// NSCopying protocol
- (id) copyWithZone: (NSZone *)zone 
{
  NSToolbarItem *new = [[NSToolbarItem allocWithZone: zone] 
    initWithItemIdentifier: _itemIdentifier];
  NSString *toolTip;
  NSImage *image;
  NSString *label;
  NSMenuItem *item;

  // Copy all items individually...
  [new setTarget: [self target]];
  [new setAction: [self action]];

  toolTip = [[self toolTip] copyWithZone: zone];
  [new setToolTip: toolTip];
  RELEASE(toolTip);
  [new setTag: [self tag]];
  image = [[self image] copyWithZone: zone];
  [new setImage: image];
  RELEASE(image);
  [new setEnabled: [self isEnabled]];
  label = [[self paletteLabel] copyWithZone: zone];
  [new setPaletteLabel: label];
  RELEASE(label);
  label = [[self label] copyWithZone: zone];
  [new setLabel: label];
  RELEASE(label);
  [new setMinSize: [self minSize]];
  [new setMaxSize: [self maxSize]];
  [new setAutovalidates: [self autovalidates]];
  [new setVisibilityPriority: [self visibilityPriority]];
  item = [[self menuFormRepresentation] copyWithZone: zone];
  [new setMenuFormRepresentation: item];
  RELEASE(item);

  if ([self view] != nil)
    {
      NSData *encodedView = nil;
      NSView *superview = nil;

      /* NSView doesn't implement -copyWithZone:, that's why we encode
         then decode the view to create a copy of it. */
      superview = [[self view] superview];
      /* We must avoid to encode view hierarchy */
      [[self view] removeFromSuperviewWithoutNeedingDisplay];
      NSLog(@"Encode toolbar item with label %@, view %@ and superview %@", 
            [self label], [self view], superview);
      // NOTE: Keyed archiver would fail on NSSlider here.
      encodedView = [NSArchiver archivedDataWithRootObject: [self view]];
      [new setView: [NSUnarchiver unarchiveObjectWithData: encodedView]];
      // Re-add the view to its hierarchy
      [superview addSubview: [self view]];
    }

  return new;
}

- (NSString *) description
{
  return [NSString stringWithFormat: @"<%@ - <%@>>",[super description],[self itemIdentifier]];
}
@end
