libs-gui/Source/GSTheme.m
ericwa 450a7402db * Source/GSTheme.m: Remove code which sets and unsets images
on theme activation/deactivation.
* Source/NSImage.m (+imageNamed:): Factor out code for finding
the path for a name to a separate method, +pathForImageNamed.
The code is unchanged otherwise, except for fixing the retrieval
of images from the theme bundle, which was broken.
* Source/NSImage.m (-setName:): Remove code for creating theme
proxy. Subscribe/unscribe to theme change notification when
the receiver is added/removed from the name dictionary.
* Source/NSImage.m (-themeDidActivate:): Method called in
response to a GSThemeDidActivateNotification on images with
a name set. It does a path lookup in the same way that
+imageNamed: would, and checks if the path has changed due
to the theme change. If it has, all reps are discarded and
the image at the new path is loaded. This avoids the need for
the theme proxy objects.


git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/gui/trunk@34474 72102866-910b-0410-8b05-ffd578937521
2012-01-10 04:38:10 +00:00

1263 lines
32 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"
// 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 *GSMenuHorizontalItem = @"GSMenuHorizontalItem";
NSString *GSMenuVerticalItem = @"GSMenuVerticalItem";
NSString *GSMenuSeparatorItem = @"GSMenuSeparatorItem";
// Progress indicator part names
NSString *GSProgressIndicatorBarDeterminate
= @"GSProgressIndicatorBarDeterminate";
// Color well part names
NSString *GSColorWell = @"GSColorWell";
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;
}
}
@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 NSString *currentThemeName = 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];
NSMutableDictionary *images;
NSMutableDictionary *oldImages;
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 _images _internal->images
#define _oldImages _internal->oldImages
#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 (name != currentThemeName && [name isEqual: currentThemeName] == NO)
{
[self setTheme: [self loadThemeNamed: name]];
ASSIGN(currentThemeName, name); // Don't try to load again.
}
}
+ (void) initialize
{
if (themes == nil)
{
themes = [NSMutableDictionary new];
null = RETAIN([NSNull null]);
defaultTheme = [[self alloc] initWithBundle: nil];
ASSIGN(theTheme, defaultTheme);
ASSIGN(currentThemeName, [defaultTheme name]);
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 length] == 0)
{
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"];
}
if ([theme isEqualToString: @"GNUstep.theme"] == YES)
{
return defaultTheme;
}
}
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];
DESTROY(currentThemeName);
ASSIGN (theTheme, theme);
[theTheme activate];
ASSIGN(currentThemeName, [theTheme name]);
/*
* 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;
}
/*
* 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] = [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 = [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(_images);
RELEASE(_oldImages);
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);
_images = [NSMutableDictionary new];
_oldImages = [NSMutableDictionary 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 GSThemeDisabledState:
fullName = [aName stringByAppendingString: @"Disabled"];
break;
case GSThemeHighlightedState:
fullName = [aName stringByAppendingString: @"Highlighted"];
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])
{
tiles = [[GSDrawTiles alloc]
initWithNinePatchImage: image];
[tiles setFillStyle: GSThemeFillStyleScaleAll];
}
else
{
tiles = [[GSDrawTiles alloc] initWithImage: image
horizontal: x
vertical: y];
[tiles setFillStyle: style];
}
RELEASE(image);
}
}
}
else
{
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"];
}
@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