mirror of
https://github.com/gnustep/libs-gui.git
synced 2025-04-26 08:20:55 +00:00
part name, instead themes should just provide a part called NSScrollView. Add -scrollViewScrollersOverlapBorders method. * Source/GSTheme.m: Remove GSScrollViewBottomCorner part name. * Source/GSThemeDrawing.m: Add -scrollViewScrollersOverlapBorders. * Source/GSThemeDrawing.m (-drawBrowserRect:...): If -scrollViewScrollersOverlapBorders is enabled, fill the browser background with the NSScrollView tile. * Source/GSThemeDrawing.m (-drawScrollViewRect:...): If -scrollViewScrollersOverlapBorders is enabled, fill the scroll view background with the NSScrollView tile. * Source/NSScroller.m (-rectForPart:): Change the meaning of the GSScrollerKnobOvershoot default so the knob only overlaps the buttons by this much (rather than both ends of the track). Turns out this is more useful for themes. * Source/NSScrollView.m (-tile): Add support for -[GSTheme scrollViewScrollersOverlapBorders] * Source/NSBrowser.m (-tile): Add support for -[GSTheme scrollViewScrollersOverlapBorders] and -[GSTheme scrollViewUseBottomCorner] The overall point of these additions is to support NSScrollView and NSBrowser looking like: http://jesseross.com/clients/gnustep/ui/concepts/ git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/gui/trunk@37238 72102866-910b-0410-8b05-ffd578937521
1356 lines
35 KiB
Objective-C
1356 lines
35 KiB
Objective-C
/** <title>GSTheme</title>
|
|
|
|
<abstract>Useful/configurable drawing functions</abstract>
|
|
|
|
Copyright (C) 2004 Free Software Foundation, Inc.
|
|
|
|
Author: Adam Fedor <fedor@gnu.org>
|
|
Date: Jan 2004
|
|
|
|
This file is part of the GNU Objective C User interface 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/NSBundle.h>
|
|
#import <Foundation/NSDebug.h>
|
|
#import <Foundation/NSDictionary.h>
|
|
#import <Foundation/NSException.h>
|
|
#import <Foundation/NSFileManager.h>
|
|
#import <Foundation/NSInvocation.h>
|
|
#import <Foundation/NSMapTable.h>
|
|
#import <Foundation/NSMethodSignature.h>
|
|
#import <Foundation/NSNotification.h>
|
|
#import <Foundation/NSNull.h>
|
|
#import <Foundation/NSPathUtilities.h>
|
|
#import <Foundation/NSSet.h>
|
|
#import <Foundation/NSUserDefaults.h>
|
|
#import "GNUstepBase/GSObjCRuntime.h"
|
|
#import "GNUstepGUI/GSTheme.h"
|
|
#import "AppKit/NSApplication.h"
|
|
#import "AppKit/NSButtonCell.h"
|
|
#import "AppKit/NSButton.h"
|
|
#import "AppKit/NSColor.h"
|
|
#import "AppKit/NSColorList.h"
|
|
#import "AppKit/NSGraphics.h"
|
|
#import "AppKit/NSImage.h"
|
|
#import "AppKit/NSImageView.h"
|
|
#import "AppKit/NSMatrix.h"
|
|
#import "AppKit/NSMenu.h"
|
|
#import "AppKit/NSPanel.h"
|
|
#import "AppKit/NSScrollView.h"
|
|
#import "AppKit/NSSegmentedControl.h"
|
|
#import "AppKit/NSTextContainer.h"
|
|
#import "AppKit/NSTextField.h"
|
|
#import "AppKit/NSTextView.h"
|
|
#import "AppKit/NSScrollView.h"
|
|
#import "AppKit/NSView.h"
|
|
#import "AppKit/NSWindow.h"
|
|
#import "AppKit/NSBezierPath.h"
|
|
#import "AppKit/PSOperators.h"
|
|
#import "GSThemePrivate.h"
|
|
|
|
NSString *GSSwitch = @"GSSwitch";
|
|
NSString *GSRadio = @"GSRadio";
|
|
|
|
// Scroller part names
|
|
NSString *GSScrollerDownArrow = @"GSScrollerDownArrow";
|
|
NSString *GSScrollerHorizontalKnob = @"GSScrollerHorizontalKnob";
|
|
NSString *GSScrollerHorizontalSlot = @"GSScrollerHorizontalSlot";
|
|
NSString *GSScrollerLeftArrow = @"GSScrollerLeftArrow";
|
|
NSString *GSScrollerRightArrow = @"GSScrollerRightArrow";
|
|
NSString *GSScrollerUpArrow = @"GSScrollerUpArrow";
|
|
NSString *GSScrollerVerticalKnob = @"GSScrollerVerticalKnob";
|
|
NSString *GSScrollerVerticalSlot = @"GSScrollerVerticalSlot";
|
|
|
|
// Table view part names
|
|
NSString *GSTableHeader = @"GSTableHeader";
|
|
NSString *GSTableCorner = @"GSTableCorner";
|
|
|
|
// Browser part names
|
|
NSString *GSBrowserHeader = @"GSBrowserHeader";
|
|
|
|
// Menu part names
|
|
NSString *GSMenuHorizontalBackground = @"GSMenuHorizontalBackground";
|
|
NSString *GSMenuVerticalBackground = @"GSMenuVerticalBackground";
|
|
NSString *GSMenuTitleBackground = @"GSMenuTitleBackground";
|
|
NSString *GSMenuHorizontalItem = @"GSMenuHorizontalItem";
|
|
NSString *GSMenuVerticalItem = @"GSMenuVerticalItem";
|
|
NSString *GSMenuSeparatorItem = @"GSMenuSeparatorItem";
|
|
|
|
// NSPopUpButton part names
|
|
NSString *GSPopUpButton = @"GSPopUpButton";
|
|
|
|
// Progress indicator part names
|
|
NSString *GSProgressIndicatorBezel = @"GSProgressIndicatorBezel";
|
|
NSString *GSProgressIndicatorBarDeterminate
|
|
= @"GSProgressIndicatorBarDeterminate";
|
|
|
|
// Color well part names
|
|
NSString *GSColorWell = @"GSColorWell";
|
|
|
|
// Slider part names
|
|
NSString *GSSliderHorizontalTrack = @"GSSliderHorizontalTrack";
|
|
NSString *GSSliderVerticalTrack = @"GSSliderVerticalTrack";
|
|
|
|
// NSBox parts
|
|
NSString *GSBoxBorder = @"GSBoxBorder";
|
|
|
|
/* NSTabView parts */
|
|
NSString *GSTabViewSelectedTabFill = @"GSTabViewSelectedTabFill";
|
|
NSString *GSTabViewUnSelectedTabFill = @"GSTabViewUnSelectedTabFill";
|
|
NSString *GSTabViewBackgroundTabFill = @"GSTabViewBackgroundTabFill";
|
|
NSString *GSTabViewBottomSelectedTabFill = @"GSTabViewBottomSelectedTabFill";
|
|
NSString *GSTabViewBottomUnSelectedTabFill = @"GSTabViewBottomUnSelectedTabFill";
|
|
NSString *GSTabViewBottomBackgroundTabFill = @"GSTabViewBottomBackgroundTabFill";
|
|
NSString *GSTabViewLeftSelectedTabFill = @"GSTabViewLeftSelectedTabFill";
|
|
NSString *GSTabViewLeftUnSelectedTabFill = @"GSTabViewLeftUnSelectedTabFill";
|
|
NSString *GSTabViewLeftBackgroundTabFill = @"GSTabViewLeftBackgroundTabFill";
|
|
NSString *GSTabViewRightSelectedTabFill = @"GSTabViewRightSelectedTabFill";
|
|
NSString *GSTabViewRightUnSelectedTabFill = @"GSTabViewRightUnSelectedTabFill";
|
|
NSString *GSTabViewRightBackgroundTabFill = @"GSTabViewRightBackgroundTabFill";
|
|
|
|
|
|
NSString *GSThemeDidActivateNotification
|
|
= @"GSThemeDidActivateNotification";
|
|
NSString *GSThemeDidDeactivateNotification
|
|
= @"GSThemeDidDeactivateNotification";
|
|
NSString *GSThemeWillActivateNotification
|
|
= @"GSThemeWillActivateNotification";
|
|
NSString *GSThemeWillDeactivateNotification
|
|
= @"GSThemeWillDeactivateNotification";
|
|
|
|
NSString *
|
|
GSThemeStringFromFillStyle(GSThemeFillStyle s)
|
|
{
|
|
switch (s)
|
|
{
|
|
case GSThemeFillStyleNone: return @"None";
|
|
case GSThemeFillStyleScale: return @"Scale";
|
|
case GSThemeFillStyleRepeat: return @"Repeat";
|
|
case GSThemeFillStyleCenter: return @"Center";
|
|
case GSThemeFillStyleMatrix: return @"Matrix";
|
|
case GSThemeFillStyleScaleAll: return @"ScaleAll";
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
GSThemeFillStyle
|
|
GSThemeFillStyleFromString(NSString *s)
|
|
{
|
|
if (s == nil || [s isEqualToString: @"None"])
|
|
{
|
|
return GSThemeFillStyleNone;
|
|
}
|
|
if ([s isEqualToString: @"Scale"])
|
|
{
|
|
return GSThemeFillStyleScale;
|
|
}
|
|
if ([s isEqualToString: @"Repeat"])
|
|
{
|
|
return GSThemeFillStyleRepeat;
|
|
}
|
|
if ([s isEqualToString: @"Center"])
|
|
{
|
|
return GSThemeFillStyleCenter;
|
|
}
|
|
if ([s isEqualToString: @"Matrix"])
|
|
{
|
|
return GSThemeFillStyleMatrix;
|
|
}
|
|
if ([s isEqualToString: @"ScaleAll"])
|
|
{
|
|
return GSThemeFillStyleScaleAll;
|
|
}
|
|
return GSThemeFillStyleNone;
|
|
}
|
|
|
|
NSString *
|
|
GSStringFromSegmentStyle(NSSegmentStyle segmentStyle)
|
|
{
|
|
switch (segmentStyle)
|
|
{
|
|
case NSSegmentStyleAutomatic: return @"NSSegmentStyleAutomatic";
|
|
case NSSegmentStyleRounded: return @"NSSegmentStyleRounded";
|
|
case NSSegmentStyleTexturedRounded: return @"NSSegmentStyleTexturedRounded";
|
|
case NSSegmentStyleRoundRect: return @"NSSegmentStyleRoundRect";
|
|
case NSSegmentStyleTexturedSquare: return @"NSSegmentStyleTexturedSquare";
|
|
case NSSegmentStyleCapsule: return @"NSSegmentStyleCapsule";
|
|
case NSSegmentStyleSmallSquare: return @"NSSegmentStyleSmallSquare";
|
|
default: return nil;
|
|
}
|
|
}
|
|
|
|
NSString *
|
|
GSStringFromBezelStyle(NSBezelStyle bezelStyle)
|
|
{
|
|
switch (bezelStyle)
|
|
{
|
|
case NSRoundedBezelStyle: return @"NSRoundedBezelStyle";
|
|
case NSRegularSquareBezelStyle: return @"NSRegularSquareBezelStyle";
|
|
case NSThickSquareBezelStyle: return @"NSThickSquareBezelStyle";
|
|
case NSThickerSquareBezelStyle: return @"NSThickerSquareBezelStyle";
|
|
case NSDisclosureBezelStyle: return @"NSDisclosureBezelStyle";
|
|
case NSShadowlessSquareBezelStyle: return @"NSShadowlessSquareBezelStyle";
|
|
case NSCircularBezelStyle: return @"NSCircularBezelStyle";
|
|
case NSTexturedSquareBezelStyle: return @"NSTexturedSquareBezelStyle";
|
|
case NSHelpButtonBezelStyle: return @"NSHelpButtonBezelStyle";
|
|
case NSSmallSquareBezelStyle: return @"NSSmallSquareBezelStyle";
|
|
case NSTexturedRoundedBezelStyle: return @"NSTexturedRoundedBezelStyle";
|
|
case NSRoundRectBezelStyle: return @"NSRoundRectBezelStyle";
|
|
case NSRecessedBezelStyle: return @"NSRecessedBezelStyle";
|
|
case NSRoundedDisclosureBezelStyle: return @"NSRoundedDisclosureBezelStyle";
|
|
case NSNeXTBezelStyle: return @"NSNeXTBezelStyle";
|
|
case NSPushButtonBezelStyle: return @"NSPushButtonBezelStyle";
|
|
case NSSmallIconButtonBezelStyle: return @"NSSmallIconButtonBezelStyle";
|
|
case NSMediumIconButtonBezelStyle: return @"NSMediumIconButtonBezelStyle";
|
|
case NSLargeIconButtonBezelStyle: return @"NSLargeIconButtonBezelStyle";
|
|
default: return nil;
|
|
}
|
|
}
|
|
|
|
NSString *
|
|
GSStringFromBorderType(NSBorderType borderType)
|
|
{
|
|
switch (borderType)
|
|
{
|
|
case NSNoBorder: return @"NSNoBorder";
|
|
case NSLineBorder: return @"NSLineBorder";
|
|
case NSBezelBorder: return @"NSBezelBorder";
|
|
case NSGrooveBorder: return @"NSGrooveBorder";
|
|
default: return nil;
|
|
}
|
|
}
|
|
|
|
NSString *
|
|
GSStringFromTabViewType(NSTabViewType type)
|
|
{
|
|
switch (type)
|
|
{
|
|
case NSTopTabsBezelBorder: return @"NSTopTabsBezelBorder";
|
|
case NSBottomTabsBezelBorder: return @"NSBottomTabsBezelBorder";
|
|
case NSLeftTabsBezelBorder: return @"NSLeftTabsBezelBorder";
|
|
case NSRightTabsBezelBorder: return @"NSRightTabsBezelBorder";
|
|
case NSNoTabsBezelBorder: return @"NSNoTabsBezelBorder";
|
|
case NSNoTabsLineBorder: return @"NSNoTabsLineBorder";
|
|
case NSNoTabsNoBorder: return @"NSNoTabsNoBorder";
|
|
default: return nil;
|
|
}
|
|
}
|
|
|
|
@interface NSImage (Private)
|
|
+ (void) _setImagePath: (NSString*)path name: (NSString*)name;
|
|
+ (void) _reloadCachedImages;
|
|
@end
|
|
|
|
@interface GSTheme (Private)
|
|
- (void) _revokeOwnerships;
|
|
@end
|
|
|
|
/* This private internal class is used to store information about a method
|
|
* in some other class which is overridden while the current theme is
|
|
* active.
|
|
*/
|
|
@interface GSThemeMethod : NSObject
|
|
{
|
|
@public
|
|
Class cls;
|
|
SEL sel;
|
|
IMP imp; // The new method implementation
|
|
IMP old; // The original method implementation
|
|
Method mth; // The method information
|
|
}
|
|
@end
|
|
|
|
@implementation GSThemeMethod
|
|
@end
|
|
|
|
@implementation GSTheme
|
|
|
|
static GSTheme *defaultTheme = nil;
|
|
static GSTheme *theTheme = nil;
|
|
static NSMutableDictionary *themes = nil;
|
|
static NSNull *null = nil;
|
|
static NSMapTable *names = 0;
|
|
|
|
typedef struct {
|
|
NSBundle *bundle;
|
|
NSColorList *colors;
|
|
NSColorList *extraColors[GSThemeSelectedState+1];
|
|
NSMutableSet *imageNames;
|
|
NSMutableDictionary *tiles[GSThemeSelectedState+1];
|
|
NSMutableSet *owned;
|
|
NSImage *icon;
|
|
NSString *name;
|
|
Class colorClass;
|
|
Class imageClass;
|
|
NSMutableArray *overrides;
|
|
} internal;
|
|
|
|
#define _internal ((internal*)_reserved)
|
|
#define _bundle _internal->bundle
|
|
#define _colors _internal->colors
|
|
#define _extraColors _internal->extraColors
|
|
#define _imageNames _internal->imageNames
|
|
#define _tiles _internal->tiles
|
|
#define _owned _internal->owned
|
|
#define _icon _internal->icon
|
|
#define _name _internal->name
|
|
#define _colorClass _internal->colorClass
|
|
#define _imageClass _internal->imageClass
|
|
#define _overrides _internal->overrides
|
|
|
|
+ (void) defaultsDidChange: (NSNotification*)n
|
|
{
|
|
NSUserDefaults *defs;
|
|
NSString *name;
|
|
|
|
defs = [NSUserDefaults standardUserDefaults];
|
|
name = [defs stringForKey: @"GSTheme"];
|
|
if (0 == [name length])
|
|
{
|
|
name = @"GNUstep";
|
|
}
|
|
else if ([[name pathExtension] isEqual: @"theme"])
|
|
{
|
|
name = [name stringByDeletingPathExtension];
|
|
}
|
|
if (NO == [[name lastPathComponent] isEqual: [theTheme name]])
|
|
{
|
|
[self setTheme: [self loadThemeNamed: name]];
|
|
}
|
|
}
|
|
|
|
+ (void) initialize
|
|
{
|
|
if (themes == nil)
|
|
{
|
|
themes = [NSMutableDictionary new];
|
|
null = RETAIN([NSNull null]);
|
|
defaultTheme = [[self alloc] initWithBundle: nil];
|
|
ASSIGN(theTheme, defaultTheme);
|
|
names = NSCreateMapTable(NSNonOwnedPointerMapKeyCallBacks,
|
|
NSIntMapValueCallBacks, 0);
|
|
/* Establish the theme specified by the user defaults (if any);
|
|
*/
|
|
[self defaultsDidChange: nil];
|
|
}
|
|
}
|
|
|
|
+ (GSTheme*) loadThemeNamed: (NSString*)aName
|
|
{
|
|
NSBundle *bundle;
|
|
Class cls;
|
|
GSTheme *instance;
|
|
NSString *theme;
|
|
|
|
if ([[aName pathExtension] isEqual: @"theme"])
|
|
{
|
|
aName = [aName stringByDeletingPathExtension];
|
|
}
|
|
if ([aName length] == 0 || [[aName lastPathComponent] isEqual: @"GNUstep"])
|
|
{
|
|
return defaultTheme;
|
|
}
|
|
|
|
if ([aName isAbsolutePath] == YES)
|
|
{
|
|
theme = aName;
|
|
}
|
|
else
|
|
{
|
|
aName = [aName lastPathComponent];
|
|
}
|
|
|
|
/* Ensure that the theme name has the 'theme' extension.
|
|
*/
|
|
if ([[aName pathExtension] isEqualToString: @"theme"] == YES)
|
|
{
|
|
theme = aName;
|
|
}
|
|
else
|
|
{
|
|
theme = [aName stringByAppendingPathExtension: @"theme"];
|
|
}
|
|
|
|
bundle = [themes objectForKey: theme];
|
|
if (bundle == nil)
|
|
{
|
|
NSString *path = nil;
|
|
NSFileManager *mgr = [NSFileManager defaultManager];
|
|
BOOL isDir;
|
|
|
|
/* A theme may be either an absolute path or a filename to be located
|
|
* in the Themes subdirectory of one of the standard Library directories.
|
|
*/
|
|
if ([theme isAbsolutePath] == YES)
|
|
{
|
|
if ([mgr fileExistsAtPath: theme isDirectory: &isDir] == YES
|
|
&& isDir == YES)
|
|
{
|
|
path = theme;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
NSEnumerator *enumerator;
|
|
|
|
enumerator = [NSSearchPathForDirectoriesInDomains
|
|
(NSAllLibrariesDirectory, NSAllDomainsMask, YES) objectEnumerator];
|
|
while ((path = [enumerator nextObject]) != nil)
|
|
{
|
|
path = [path stringByAppendingPathComponent: @"Themes"];
|
|
path = [path stringByAppendingPathComponent: theme];
|
|
if ([mgr fileExistsAtPath: path isDirectory: &isDir])
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (path == nil)
|
|
{
|
|
NSLog (@"No theme named '%@' found", aName);
|
|
return nil;
|
|
}
|
|
else
|
|
{
|
|
bundle = [NSBundle bundleWithPath: path];
|
|
[themes setObject: bundle forKey: theme];
|
|
[bundle load]; // Ensure code is loaded.
|
|
}
|
|
}
|
|
|
|
cls = [bundle principalClass];
|
|
if (cls == 0)
|
|
{
|
|
cls = self;
|
|
}
|
|
instance = [[cls alloc] initWithBundle: bundle];
|
|
return AUTORELEASE(instance);
|
|
}
|
|
|
|
+ (void) orderFrontSharedThemePanel: (id)sender
|
|
{
|
|
GSThemePanel *panel;
|
|
|
|
panel = [GSThemePanel sharedThemePanel];
|
|
[panel update: self];
|
|
[panel center];
|
|
[panel orderFront: self];
|
|
}
|
|
|
|
+ (void) setTheme: (GSTheme*)theme
|
|
{
|
|
if (theme == nil)
|
|
{
|
|
theme = defaultTheme;
|
|
}
|
|
if (theme != theTheme)
|
|
{
|
|
/*
|
|
* Remove any previous observers...
|
|
*/
|
|
[[NSNotificationCenter defaultCenter]
|
|
removeObserver: self];
|
|
|
|
[theTheme deactivate];
|
|
ASSIGN (theTheme, theme);
|
|
[theTheme activate];
|
|
|
|
/*
|
|
* Listen to notifications...
|
|
*/
|
|
[[NSNotificationCenter defaultCenter]
|
|
addObserver: self
|
|
selector: @selector(defaultsDidChange:)
|
|
name: NSUserDefaultsDidChangeNotification
|
|
object: nil];
|
|
}
|
|
}
|
|
|
|
+ (GSTheme*) theme
|
|
{
|
|
return theTheme;
|
|
}
|
|
|
|
- (void) activate
|
|
{
|
|
NSUserDefaults *defs;
|
|
NSMutableArray *searchList;
|
|
NSEnumerator *enumerator;
|
|
NSDictionary *infoDict;
|
|
NSWindow *window;
|
|
GSThemeControlState state;
|
|
|
|
NSDebugMLLog(@"GSTheme", @"%@ %p", [self name], self);
|
|
/* Get rid of any cached colors list so that we regenerate it when needed
|
|
*/
|
|
[_colors release];
|
|
_colors = nil;
|
|
for (state = 0; state <= GSThemeSelectedState; state++)
|
|
{
|
|
[_extraColors[state] release];
|
|
_extraColors[state] = nil;
|
|
}
|
|
|
|
/*
|
|
* Reload NSImage's cache of image by name
|
|
*/
|
|
[NSImage _reloadCachedImages];
|
|
|
|
/*
|
|
* Use the GSThemeDomain key in the info dictionary of the theme to
|
|
* set a defaults domain which will establish user defaults values
|
|
* but will not override any defaults set explicitly by the user.
|
|
* NB. For subclasses, the theme info dictionary may not be the same
|
|
* as that of the bundle, so we don't use the bundle method directly.
|
|
*/
|
|
infoDict = [self infoDictionary];
|
|
defs = [NSUserDefaults standardUserDefaults];
|
|
searchList = [[defs searchList] mutableCopy];
|
|
if ([[infoDict objectForKey: @"GSThemeDomain"] isKindOfClass:
|
|
[NSDictionary class]] == YES)
|
|
{
|
|
[defs removeVolatileDomainForName: @"GSThemeDomain"];
|
|
[defs setVolatileDomain: [infoDict objectForKey: @"GSThemeDomain"]
|
|
forName: @"GSThemeDomain"];
|
|
if ([searchList containsObject: @"GSThemeDomain"] == NO)
|
|
{
|
|
NSUInteger index;
|
|
|
|
/*
|
|
* Higher priority than GSConfigDomain and NSRegistrationDomain,
|
|
* but lower than NSGlobalDomain, NSArgumentDomain, and others
|
|
* set by the user to be application specific.
|
|
*/
|
|
index = [searchList indexOfObject: GSConfigDomain];
|
|
if (index == NSNotFound)
|
|
{
|
|
index = [searchList indexOfObject: NSRegistrationDomain];
|
|
if (index == NSNotFound)
|
|
{
|
|
index = [searchList count];
|
|
}
|
|
}
|
|
[searchList insertObject: @"GSThemeDomain" atIndex: index];
|
|
[defs setSearchList: searchList];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
[searchList removeObject: @"GSThemeDomain"];
|
|
[defs removeVolatileDomainForName: @"GSThemeDomain"];
|
|
}
|
|
RELEASE(searchList);
|
|
|
|
/* Install any overridden methods.
|
|
*/
|
|
if (_overrides != nil)
|
|
{
|
|
NSEnumerator *e = [_overrides objectEnumerator];
|
|
GSThemeMethod *m;
|
|
|
|
while ((m = [e nextObject]) != nil)
|
|
{
|
|
method_setImplementation(m->mth, m->imp);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Tell subclass that basic activation is done and it can do its own.
|
|
*/
|
|
[[NSNotificationCenter defaultCenter]
|
|
postNotificationName: GSThemeWillActivateNotification
|
|
object: self
|
|
userInfo: nil];
|
|
|
|
/*
|
|
* Tell all other classes that new theme information is present.
|
|
*/
|
|
[[NSNotificationCenter defaultCenter]
|
|
postNotificationName: GSThemeDidActivateNotification
|
|
object: self
|
|
userInfo: nil];
|
|
|
|
/*
|
|
* Reset main menu to change between styles if necessary
|
|
*/
|
|
[[NSApp mainMenu] setMain: YES];
|
|
|
|
/*
|
|
* Mark all windows as needing redisplaying to show the new theme.
|
|
*/
|
|
enumerator = [[NSApp windows] objectEnumerator];
|
|
while ((window = [enumerator nextObject]) != nil)
|
|
{
|
|
[[[window contentView] superview] setNeedsDisplay: YES];
|
|
}
|
|
}
|
|
|
|
- (NSArray*) authors
|
|
{
|
|
return [[self infoDictionary] objectForKey: @"GSThemeAuthors"];
|
|
}
|
|
|
|
- (NSBundle*) bundle
|
|
{
|
|
return _bundle;
|
|
}
|
|
|
|
- (Class) colorClass
|
|
{
|
|
return [NSColorList class];
|
|
}
|
|
|
|
- (void) colorFlush: (NSString*)aName
|
|
state: (GSThemeControlState)elementState
|
|
{
|
|
int pos;
|
|
int end;
|
|
|
|
if (elementState > GSThemeSelectedState)
|
|
{
|
|
pos = 0;
|
|
end = GSThemeSelectedState;
|
|
}
|
|
else
|
|
{
|
|
pos = elementState;
|
|
end = elementState;
|
|
}
|
|
while (pos <= end)
|
|
{
|
|
if (_extraColors[pos] != nil)
|
|
{
|
|
[_extraColors[pos] release];
|
|
_extraColors[pos] = nil;
|
|
}
|
|
pos++;
|
|
}
|
|
}
|
|
|
|
- (NSColor*) colorNamed: (NSString*)aName
|
|
state: (GSThemeControlState)elementState
|
|
{
|
|
NSColor *c = nil;
|
|
|
|
NSAssert(elementState <= GSThemeSelectedState, NSInvalidArgumentException);
|
|
NSAssert(elementState >= 0, NSInvalidArgumentException);
|
|
|
|
if (aName != nil)
|
|
{
|
|
if (_extraColors[elementState] == nil)
|
|
{
|
|
NSString *colorsPath;
|
|
NSString *listName;
|
|
NSString *resourceName;
|
|
|
|
/* Attempt to load color list ... if the list is not found
|
|
* or the load fails, set a null marker.
|
|
*/
|
|
switch (elementState)
|
|
{
|
|
default:
|
|
case GSThemeNormalState:
|
|
listName = @"ThemeExtra";
|
|
break;
|
|
case GSThemeHighlightedState:
|
|
listName = @"ThemeExtraHighlighted";
|
|
break;
|
|
case GSThemeSelectedState:
|
|
listName = @"ThemeExtraSelected";
|
|
break;
|
|
}
|
|
resourceName = [listName stringByAppendingString: @"Colors"];
|
|
colorsPath = [_bundle pathForResource: resourceName
|
|
ofType: @"clr"];
|
|
if (colorsPath != nil)
|
|
{
|
|
_extraColors[elementState]
|
|
= [[_colorClass alloc] initWithName: listName
|
|
fromFile: colorsPath];
|
|
/* If the list is actually empty, we get rid of it to avoid
|
|
* unnecessary lookups.
|
|
*/
|
|
if ([[_extraColors[elementState] allKeys] count] == 0)
|
|
{
|
|
[_extraColors[elementState] release];
|
|
_extraColors[elementState] = nil;
|
|
}
|
|
}
|
|
if (_extraColors[elementState] == nil)
|
|
{
|
|
_extraColors[elementState] = (id)[null retain];
|
|
}
|
|
}
|
|
if (_extraColors[elementState] != (id)null)
|
|
{
|
|
c = [_extraColors[elementState] colorWithKey: aName];
|
|
}
|
|
}
|
|
return c;
|
|
}
|
|
|
|
- (NSColorList*) colors
|
|
{
|
|
if (_colors == nil)
|
|
{
|
|
NSString *colorsPath;
|
|
|
|
colorsPath = [_bundle pathForResource: @"ThemeColors" ofType: @"clr"];
|
|
if (colorsPath == nil)
|
|
{
|
|
_colors = (id)[null retain];
|
|
}
|
|
else
|
|
{
|
|
_colors = [[_colorClass alloc] initWithName: @"System"
|
|
fromFile: colorsPath];
|
|
}
|
|
}
|
|
if ((id)_colors == (id)null)
|
|
{
|
|
return nil;
|
|
}
|
|
return _colors;
|
|
}
|
|
|
|
- (void) deactivate
|
|
{
|
|
NSDebugMLLog(@"GSTheme", @"%@ %p", [self name], self);
|
|
|
|
/* Tell everything that we will become inactive.
|
|
*/
|
|
[[NSNotificationCenter defaultCenter]
|
|
postNotificationName: GSThemeWillDeactivateNotification
|
|
object: self
|
|
userInfo: nil];
|
|
|
|
/* Remove any overridden methods.
|
|
*/
|
|
if (_overrides != nil)
|
|
{
|
|
NSEnumerator *e = [_overrides objectEnumerator];
|
|
GSThemeMethod *m;
|
|
|
|
while ((m = [e nextObject]) != nil)
|
|
{
|
|
method_setImplementation(m->mth, m->old);
|
|
}
|
|
}
|
|
|
|
[self _revokeOwnerships];
|
|
|
|
/* Tell everything that we have become inactive.
|
|
*/
|
|
[[NSNotificationCenter defaultCenter]
|
|
postNotificationName: GSThemeDidDeactivateNotification
|
|
object: self
|
|
userInfo: nil];
|
|
}
|
|
|
|
- (void) dealloc
|
|
{
|
|
if (_reserved != 0)
|
|
{
|
|
GSThemeControlState state;
|
|
|
|
for (state = 0; state <= GSThemeSelectedState; state++)
|
|
{
|
|
RELEASE(_extraColors[state]);
|
|
RELEASE(_tiles[state]);
|
|
}
|
|
RELEASE(_bundle);
|
|
RELEASE(_colors);
|
|
RELEASE(_imageNames);
|
|
RELEASE(_icon);
|
|
[self _revokeOwnerships];
|
|
RELEASE(_overrides);
|
|
RELEASE(_owned);
|
|
NSZoneFree ([self zone], _reserved);
|
|
}
|
|
[super dealloc];
|
|
}
|
|
|
|
- (NSImage*) icon
|
|
{
|
|
if (_icon == nil)
|
|
{
|
|
NSString *path;
|
|
|
|
path = [[self infoDictionary] objectForKey: @"GSThemeIcon"];
|
|
if (path != nil)
|
|
{
|
|
NSString *ext = [path pathExtension];
|
|
|
|
path = [path stringByDeletingPathExtension];
|
|
path = [_bundle pathForResource: path ofType: ext];
|
|
if (path != nil)
|
|
{
|
|
_icon = [[_imageClass alloc] initWithContentsOfFile: path];
|
|
}
|
|
}
|
|
if (_icon == nil)
|
|
{
|
|
_icon = RETAIN([_imageClass imageNamed: @"GNUstep"]);
|
|
}
|
|
else
|
|
{
|
|
NSSize s = [_icon size];
|
|
float scale = 1.0;
|
|
|
|
if (s.height > 48.0)
|
|
scale = 48.0 / s.height;
|
|
if (48.0 / s.width < scale)
|
|
scale = 48.0 / s.width;
|
|
if (scale != 1.0)
|
|
{
|
|
[_icon setScalesWhenResized: YES];
|
|
s.height *= scale;
|
|
s.width *= scale;
|
|
[_icon setSize: s];
|
|
}
|
|
}
|
|
}
|
|
return _icon;
|
|
}
|
|
|
|
- (Class) imageClass
|
|
{
|
|
return [NSImage class];
|
|
}
|
|
|
|
- (id) initWithBundle: (NSBundle*)bundle
|
|
{
|
|
Class c = [self class];
|
|
unsigned int count;
|
|
Method *methods;
|
|
GSThemeMethod *mth;
|
|
GSThemeControlState state;
|
|
|
|
_reserved = NSZoneCalloc ([self zone], 1, sizeof(internal));
|
|
|
|
ASSIGN(_bundle, bundle);
|
|
_imageNames = [NSMutableSet new];
|
|
for (state = 0; state <= GSThemeSelectedState; state++)
|
|
{
|
|
_tiles[state] = [NSMutableDictionary new];
|
|
}
|
|
_owned = [NSMutableSet new];
|
|
|
|
ASSIGN(_name,
|
|
[[[_bundle bundlePath] lastPathComponent] stringByDeletingPathExtension]);
|
|
|
|
_colorClass = [self colorClass];
|
|
_imageClass = [self imageClass];
|
|
|
|
/* Now we look through our methods to find those which are actually
|
|
* replacements to override methods in other classes.
|
|
* That's determined by method name ... any method of the form
|
|
* '_override' <classname> 'Method_' <originalmethodname>
|
|
* is used to replace the original method in the class.
|
|
* We maintain dictionaries (keyed by class) for instance and class
|
|
* methods, so we can look up the original methods at runtime if the
|
|
* replacement methods want to call them.
|
|
*/
|
|
methods = class_copyMethodList(c, &count);
|
|
if (methods != NULL)
|
|
{
|
|
int counter = 0;
|
|
|
|
while (methods[counter] != 0)
|
|
{
|
|
Method method = methods[counter++];
|
|
const char *name = sel_getName(method_getName(method));
|
|
const char *ptr;
|
|
|
|
if (strncmp(name, "_override", 9) == 0
|
|
&& (ptr = strstr(name, "Method_")) > 0)
|
|
{
|
|
char buf[strlen(name)];
|
|
const char *types;
|
|
|
|
mth = [[GSThemeMethod new] autorelease];
|
|
types = method_getTypeEncoding(method);
|
|
mth->imp = method_getImplementation(method);
|
|
memcpy(buf, name + 9, (ptr - name) + 9);
|
|
buf[(ptr - name) + 9] = '\0';
|
|
mth->cls = objc_lookUpClass(buf);
|
|
if (mth->cls == 0)
|
|
{
|
|
NSLog(@"Unable to find class '%s' for '%s'", buf, name);
|
|
continue;
|
|
}
|
|
memcpy(buf, ptr + 7, strlen(ptr + 7));
|
|
buf[strlen(ptr + 7)] = '\0';
|
|
mth->sel = sel_getUid(buf);
|
|
if (mth->sel == 0)
|
|
{
|
|
NSLog(@"Unable to find selector '-%s' for '%s'", buf, name);
|
|
continue;
|
|
}
|
|
if (NO == [mth->cls instancesRespondToSelector: mth->sel])
|
|
{
|
|
NSLog(@"Instances do not respond for '%s'", name);
|
|
continue;
|
|
}
|
|
mth->old = [mth->cls instanceMethodForSelector: mth->sel];
|
|
class_addMethod(mth->cls, mth->sel, mth->imp, types);
|
|
mth->mth = class_getInstanceMethod(mth->cls, mth->sel);
|
|
|
|
if (_overrides == nil)
|
|
{
|
|
_overrides = [NSMutableArray new];
|
|
}
|
|
[_overrides addObject: mth];
|
|
}
|
|
}
|
|
free(methods);
|
|
}
|
|
|
|
methods = class_copyMethodList(object_getClass(c), &count);
|
|
if (methods != NULL)
|
|
{
|
|
int counter = 0;
|
|
|
|
while (methods[counter] != 0)
|
|
{
|
|
Method method = methods[counter++];
|
|
const char *name = sel_getName(method_getName(method));
|
|
const char *ptr;
|
|
|
|
if (strncmp(name, "_override", 9) == 0
|
|
&& (ptr = strstr(name, "Method_")) > 0)
|
|
{
|
|
char buf[strlen(name)];
|
|
const char *types;
|
|
Class cls;
|
|
|
|
mth = [[GSThemeMethod new] autorelease];
|
|
types = method_getTypeEncoding(method);
|
|
mth->imp = method_getImplementation(method);
|
|
memcpy(buf, name + 9, (ptr - name) + 9);
|
|
buf[(ptr - name) + 9] = '\0';
|
|
cls = objc_lookUpClass(buf);
|
|
if (cls == 0)
|
|
{
|
|
NSLog(@"Unable to find class '%s' for '%s'", buf, name);
|
|
continue;
|
|
}
|
|
mth->cls = object_getClass(cls);
|
|
memcpy(buf, ptr + 7, strlen(ptr + 7));
|
|
buf[strlen(ptr + 7)] = '\0';
|
|
mth->sel = sel_getUid(buf);
|
|
if (mth->sel == 0)
|
|
{
|
|
NSLog(@"Unable to find selector '-%s' for '%s'", buf, name);
|
|
continue;
|
|
}
|
|
if (NO == [cls respondsToSelector: mth->sel])
|
|
{
|
|
NSLog(@"Class does not respond for '%s'", name);
|
|
continue;
|
|
}
|
|
mth->old = [cls methodForSelector: mth->sel];
|
|
class_addMethod(mth->cls, mth->sel, mth->imp, types);
|
|
mth->mth = class_getClassMethod(cls, mth->sel);
|
|
|
|
if (_overrides == nil)
|
|
{
|
|
_overrides = [NSMutableArray new];
|
|
}
|
|
[_overrides addObject: mth];
|
|
}
|
|
}
|
|
free(methods);
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
- (NSDictionary*) infoDictionary
|
|
{
|
|
return [_bundle infoDictionary];
|
|
}
|
|
|
|
- (NSString*) name
|
|
{
|
|
if (self == defaultTheme)
|
|
{
|
|
_name = @"GNUstep";
|
|
}
|
|
return _name;
|
|
}
|
|
|
|
- (NSString*) nameForElement: (id)anObject
|
|
{
|
|
NSString *name = (NSString*)NSMapGet(names, (void*)anObject);
|
|
|
|
return name;
|
|
}
|
|
|
|
- (IMP) overriddenMethod: (SEL)selector for: (id)receiver
|
|
{
|
|
Class cls = object_getClass(receiver);
|
|
NSEnumerator *e = [_overrides objectEnumerator];
|
|
GSThemeMethod *m;
|
|
|
|
while ((m = [e nextObject]) != nil)
|
|
{
|
|
if (m->cls == cls && sel_isEqual(selector, m->sel))
|
|
{
|
|
return m->old;
|
|
}
|
|
}
|
|
return (IMP)0;
|
|
}
|
|
|
|
- (void) setName: (NSString*)aString
|
|
{
|
|
if (self != defaultTheme)
|
|
{
|
|
ASSIGNCOPY(_name, aString);
|
|
}
|
|
}
|
|
|
|
- (void) setName: (NSString*)aString
|
|
forElement: (id)anObject
|
|
temporary: (BOOL)takeOwnership
|
|
{
|
|
if (aString == nil)
|
|
{
|
|
if (anObject == nil)
|
|
{
|
|
/* Ignore this ... it's most likely a partially initialised
|
|
* control being deallocated and removing the name for a
|
|
* subsidiary item which was never allocated in the first place.
|
|
*/
|
|
return;
|
|
}
|
|
NSMapRemove(names, (void*)anObject);
|
|
[_owned removeObject: anObject];
|
|
}
|
|
else
|
|
{
|
|
if (anObject == nil)
|
|
{
|
|
[NSException raise: NSInvalidArgumentException
|
|
format: @"[%@-%@] nil object supplied",
|
|
NSStringFromClass([self class]), NSStringFromSelector(_cmd)];
|
|
}
|
|
NSMapInsert(names, (void*)anObject, (void*)aString);
|
|
if (takeOwnership == YES)
|
|
{
|
|
[_owned addObject: anObject];
|
|
}
|
|
else
|
|
{
|
|
[_owned removeObject: anObject];
|
|
}
|
|
}
|
|
}
|
|
|
|
- (NSWindow*) themeInspector
|
|
{
|
|
return [GSThemeInspector sharedThemeInspector];
|
|
}
|
|
|
|
- (void) tilesFlush: (NSString*)aName
|
|
state: (GSThemeControlState)elementState
|
|
{
|
|
int pos;
|
|
int end;
|
|
|
|
if (elementState > GSThemeSelectedState)
|
|
{
|
|
pos = 0;
|
|
end = GSThemeSelectedState;
|
|
}
|
|
else
|
|
{
|
|
pos = elementState;
|
|
end = elementState;
|
|
}
|
|
while (pos <= end)
|
|
{
|
|
NSMutableDictionary *cache;
|
|
|
|
cache = _tiles[pos++];
|
|
if (aName == nil)
|
|
{
|
|
return [cache removeAllObjects];
|
|
}
|
|
else
|
|
{
|
|
[cache removeObjectForKey: aName];
|
|
}
|
|
}
|
|
}
|
|
|
|
- (GSDrawTiles*) tilesNamed: (NSString*)aName
|
|
state: (GSThemeControlState)elementState
|
|
{
|
|
GSDrawTiles *tiles;
|
|
NSMutableDictionary *cache;
|
|
|
|
NSAssert(elementState <= GSThemeSelectedState, NSInvalidArgumentException);
|
|
NSAssert(elementState >= 0, NSInvalidArgumentException);
|
|
if (aName == nil)
|
|
{
|
|
return nil;
|
|
}
|
|
cache = _tiles[elementState];
|
|
tiles = [cache objectForKey: aName];
|
|
if (tiles == nil)
|
|
{
|
|
NSDictionary *info;
|
|
NSImage *image;
|
|
NSString *fullName;
|
|
|
|
switch (elementState)
|
|
{
|
|
default:
|
|
case GSThemeNormalState:
|
|
fullName = aName;
|
|
break;
|
|
case GSThemeFirstResponderState:
|
|
fullName = [aName stringByAppendingString: @"FirstResponder"];
|
|
break;
|
|
case GSThemeDisabledState:
|
|
fullName = [aName stringByAppendingString: @"Disabled"];
|
|
break;
|
|
case GSThemeHighlightedFirstResponderState:
|
|
fullName = [aName stringByAppendingString: @"HighlightedFirstResponder"];
|
|
break;
|
|
case GSThemeHighlightedState:
|
|
fullName = [aName stringByAppendingString: @"Highlighted"];
|
|
break;
|
|
case GSThemeSelectedFirstResponderState:
|
|
fullName = [aName stringByAppendingString: @"SelectedFirstResponder"];
|
|
break;
|
|
case GSThemeSelectedState:
|
|
fullName = [aName stringByAppendingString: @"Selected"];
|
|
break;
|
|
}
|
|
|
|
/* The GSThemeTiles entry in the info dictionary should be a
|
|
* dictionary containing information about each set of tiles.
|
|
* Keys are:
|
|
* FileName Name of the file in the ThemeTiles directory
|
|
* HorizontalDivision Where to divide the image into columns.
|
|
* VerticalDivision Where to divide the image into rows.
|
|
*/
|
|
info = [self infoDictionary];
|
|
info = [[info objectForKey: @"GSThemeTiles"] objectForKey: fullName];
|
|
if ([info isKindOfClass: [NSDictionary class]] == YES)
|
|
{
|
|
float x;
|
|
float y;
|
|
NSString *name;
|
|
NSString *path;
|
|
NSString *file;
|
|
NSString *ext;
|
|
GSThemeFillStyle style;
|
|
|
|
name = [info objectForKey: @"FillStyle"];
|
|
style = GSThemeFillStyleFromString(name);
|
|
if (style < GSThemeFillStyleNone) style = GSThemeFillStyleNone;
|
|
x = [[info objectForKey: @"HorizontalDivision"] floatValue];
|
|
y = [[info objectForKey: @"VerticalDivision"] floatValue];
|
|
file = [info objectForKey: @"FileName"];
|
|
ext = [file pathExtension];
|
|
file = [file stringByDeletingPathExtension];
|
|
path = [_bundle pathForResource: file
|
|
ofType: ext
|
|
inDirectory: @"ThemeTiles"];
|
|
if (path == nil)
|
|
{
|
|
NSLog(@"File %@.%@ not found in ThemeTiles", file, ext);
|
|
}
|
|
else
|
|
{
|
|
image = [[_imageClass alloc] initWithContentsOfFile: path];
|
|
if (image != nil)
|
|
{
|
|
if ([[info objectForKey: @"NinePatch"] boolValue]
|
|
|| [file hasSuffix: @".9"])
|
|
{
|
|
tiles = [[GSDrawTiles alloc]
|
|
initWithNinePatchImage: image];
|
|
[tiles setFillStyle: GSThemeFillStyleScaleAll];
|
|
}
|
|
else
|
|
{
|
|
tiles = [[GSDrawTiles alloc] initWithImage: image
|
|
horizontal: x
|
|
vertical: y];
|
|
[tiles setFillStyle: style];
|
|
}
|
|
RELEASE(image);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (tiles == nil)
|
|
{
|
|
NSString *imagePath;
|
|
|
|
// Try 9-patch first
|
|
imagePath = [_bundle pathForResource: fullName
|
|
ofType: @"9.png"
|
|
inDirectory: @"ThemeTiles"];
|
|
if (imagePath != nil)
|
|
{
|
|
image
|
|
= [[_imageClass alloc] initWithContentsOfFile: imagePath];
|
|
if (image != nil)
|
|
{
|
|
tiles = [[GSDrawTiles alloc]
|
|
initWithNinePatchImage: image];
|
|
[tiles setFillStyle: GSThemeFillStyleScaleAll];
|
|
RELEASE(image);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (tiles == nil)
|
|
{
|
|
NSArray *imageTypes;
|
|
NSString *imagePath;
|
|
unsigned count;
|
|
|
|
imageTypes = [_imageClass imageFileTypes];
|
|
for (count = 0; count < [imageTypes count]; count++)
|
|
{
|
|
NSString *ext = [imageTypes objectAtIndex: count];
|
|
|
|
imagePath = [_bundle pathForResource: fullName
|
|
ofType: ext
|
|
inDirectory: @"ThemeTiles"];
|
|
if (imagePath != nil)
|
|
{
|
|
image
|
|
= [[_imageClass alloc] initWithContentsOfFile: imagePath];
|
|
if (image != nil)
|
|
{
|
|
tiles = [[GSDrawTiles alloc] initWithImage: image];
|
|
RELEASE(image);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (tiles == nil)
|
|
{
|
|
[cache setObject: null forKey: aName];
|
|
}
|
|
else
|
|
{
|
|
[cache setObject: tiles forKey: aName];
|
|
RELEASE(tiles);
|
|
}
|
|
}
|
|
if (tiles == (id)null)
|
|
{
|
|
tiles = nil;
|
|
}
|
|
return tiles;
|
|
}
|
|
|
|
- (NSString*) versionString
|
|
{
|
|
return [[self infoDictionary] objectForKey: @"GSThemeVersion"];
|
|
}
|
|
|
|
- (NSString *) license
|
|
{
|
|
return [[self infoDictionary] objectForKey: @"GSThemeLicense"];
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation GSTheme (Private)
|
|
/* Remove all temporarily named objects from our registry, releasing them.
|
|
*/
|
|
- (void) _revokeOwnerships
|
|
{
|
|
id o;
|
|
|
|
while ((o = [_owned anyObject]) != nil)
|
|
{
|
|
[self setName: nil forElement: o temporary: YES];
|
|
}
|
|
}
|
|
@end
|
|
|
|
@implementation GSThemeProxy
|
|
- (id) _resource
|
|
{
|
|
return _resource;
|
|
}
|
|
- (void) _setResource: (id)resource
|
|
{
|
|
ASSIGN(_resource, resource);
|
|
}
|
|
- (void) dealloc
|
|
{
|
|
DESTROY(_resource);
|
|
[super dealloc];
|
|
}
|
|
- (NSString*) description
|
|
{
|
|
return [_resource description];
|
|
}
|
|
- (id) forwardingTargetForSelector:(SEL)aSelector
|
|
{
|
|
return _resource;
|
|
}
|
|
- (void) forwardInvocation: (NSInvocation*)anInvocation
|
|
{
|
|
[anInvocation invokeWithTarget: _resource];
|
|
}
|
|
- (NSMethodSignature*) methodSignatureForSelector: (SEL)aSelector
|
|
{
|
|
if (_resource != nil)
|
|
{
|
|
return [_resource methodSignatureForSelector: aSelector];
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* Evil hack to prevent recursion - if we are asking a remote
|
|
* object for a method signature, we can't ask it for the
|
|
* signature of methodSignatureForSelector:, so we hack in
|
|
* the signature required manually :-(
|
|
*/
|
|
if (sel_isEqual(aSelector, _cmd))
|
|
{
|
|
static NSMethodSignature *sig = nil;
|
|
|
|
if (sig == nil)
|
|
{
|
|
sig = RETAIN([NSMethodSignature signatureWithObjCTypes: "@@::"]);
|
|
}
|
|
return sig;
|
|
}
|
|
return nil;
|
|
}
|
|
}
|
|
@end
|
|
|