mirror of
https://github.com/gnustep/libs-gui.git
synced 2025-04-25 09:11:03 +00:00
Schaller <hns@computer.org>. git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/gui/trunk@24415 72102866-910b-0410-8b05-ffd578937521
2271 lines
59 KiB
Objective-C
2271 lines
59 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 Library 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
|
||
Library General Public License for more details.
|
||
|
||
You should have received a copy of the GNU Library General Public
|
||
License along with this library; if not, write to the Free
|
||
Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||
Boston, MA 02111 USA.
|
||
*/
|
||
|
||
#include "Foundation/NSBundle.h"
|
||
#include "Foundation/NSDictionary.h"
|
||
#include "Foundation/NSFileManager.h"
|
||
#include "Foundation/NSNotification.h"
|
||
#include "Foundation/NSNull.h"
|
||
#include "Foundation/NSPathUtilities.h"
|
||
#include "Foundation/NSSet.h"
|
||
#include "Foundation/NSUserDefaults.h"
|
||
#include "GNUstepGUI/GSTheme.h"
|
||
#include "AppKit/NSApplication.h"
|
||
#include "AppKit/NSButton.h"
|
||
#include "AppKit/NSColor.h"
|
||
#include "AppKit/NSColorList.h"
|
||
#include "AppKit/NSGraphics.h"
|
||
#include "AppKit/NSImage.h"
|
||
#include "AppKit/NSImageView.h"
|
||
#include "AppKit/NSMatrix.h"
|
||
#include "AppKit/NSMenu.h"
|
||
#include "AppKit/NSPanel.h"
|
||
#include "AppKit/NSScrollView.h"
|
||
#include "AppKit/NSTextContainer.h"
|
||
#include "AppKit/NSTextField.h"
|
||
#include "AppKit/NSTextView.h"
|
||
#include "AppKit/NSScrollView.h"
|
||
#include "AppKit/NSView.h"
|
||
#include "AppKit/NSWindow.h"
|
||
#include "AppKit/NSBezierPath.h"
|
||
#include "AppKit/PSOperators.h"
|
||
|
||
#include <math.h>
|
||
#include <float.h>
|
||
|
||
|
||
NSString *GSThemeDidActivateNotification
|
||
= @"GSThemeDidActivateNotification";
|
||
NSString *GSThemeDidDeactivateNotification
|
||
= @"GSThemeDidDeactivateNotification";
|
||
|
||
/** These are the nine types of tile used to draw a rectangular object.
|
||
*/
|
||
typedef enum {
|
||
TileTL = 0, /** Top left corner */
|
||
TileTM = 1, /** Top middle section */
|
||
TileTR = 2, /** Top right corner */
|
||
TileCL = 3, /** Centerj left corner */
|
||
TileCM = 4, /** Centerj middle section */
|
||
TileCR = 5, /** Centerj right corner */
|
||
TileBL = 6, /** Bottom left corner */
|
||
TileBM = 7, /** Bottom middle section */
|
||
TileBR = 8 /** Bottom right corner */
|
||
} GSThemeTileOffset;
|
||
|
||
/** This is a trivial class to hold the nine tiles needed to draw a rectangle
|
||
*/
|
||
@interface GSDrawTiles : NSObject
|
||
{
|
||
@public
|
||
NSImage *images[9]; /** The tile images */
|
||
NSRect rects[9]; /** The rectangles to use when drawing */
|
||
}
|
||
- (id) copyWithZone: (NSZone*)zone;
|
||
- (id) initWithImage: (NSImage*)image;
|
||
- (id) initWithImage: (NSImage*)image horizontal: (float)x vertical: (float)y;
|
||
- (void) scaleUp: (int)multiple;
|
||
@end
|
||
|
||
|
||
|
||
/** This is the panel used to select and inspect themes.
|
||
*/
|
||
@interface GSThemePanel : NSPanel
|
||
{
|
||
NSMatrix *matrix; // Not retained.
|
||
NSScrollView *sideView; // Not retained.
|
||
NSView *bottomView; // Not retained.
|
||
}
|
||
|
||
/** Return the shared panel.
|
||
*/
|
||
+ (GSThemePanel*) sharedThemePanel;
|
||
|
||
/** Update current theme to the one clicked on in the matrix.
|
||
*/
|
||
- (void) changeSelection: (id)sender;
|
||
|
||
/** Handle notifications
|
||
*/
|
||
- (void) notified: (NSNotification*)n;
|
||
|
||
/** Toggle whether the current theme is the default theme.
|
||
*/
|
||
- (void) setDefault: (id)sender;
|
||
|
||
/** Update list of available themes.
|
||
*/
|
||
- (void) update: (id)sender;
|
||
|
||
@end
|
||
|
||
|
||
|
||
/** This is the window used to inspect themes.
|
||
*/
|
||
@interface GSThemeInspector : NSWindow
|
||
{
|
||
}
|
||
|
||
/** Return the shared panel.
|
||
*/
|
||
+ (GSThemeInspector*) sharedThemeInspector;
|
||
|
||
/** Update to show current theme.
|
||
*/
|
||
- (void) update: (id)sender;
|
||
|
||
@end
|
||
|
||
|
||
|
||
/** This category defines private methods for internal use by GSTheme
|
||
*/
|
||
@interface GSTheme (internal)
|
||
/**
|
||
* Called whenever user defaults are changed ... this checks for the
|
||
* GSTheme user default and ensures that the specified theme is the
|
||
* current active theme.
|
||
*/
|
||
+ (void) defaultsDidChange: (NSNotification*)n;
|
||
|
||
/**
|
||
* Called to load specified theme.<br />
|
||
* If aName is nil or an empty string or 'GNUstep',
|
||
* this returns the default theme.<br />
|
||
* If the named is a full path specification, this uses that path.<br />
|
||
* Otherwise this method searches the standard locations.<br />
|
||
* Returns nil on failure.
|
||
*/
|
||
+ (GSTheme*) loadThemeNamed: (NSString*)aName;
|
||
|
||
// These two drawing method may be made public later on
|
||
- (void) drawCircularBezel: (NSRect)cellFrame withColor: (NSColor*)backgroundColor;
|
||
- (void) drawRoundBezel: (NSRect)cellFrame withColor: (NSColor*)backgroundColor;
|
||
@end
|
||
|
||
|
||
|
||
@implementation GSTheme
|
||
|
||
static GSTheme *defaultTheme = nil;
|
||
static NSString *currentThemeName = nil;
|
||
static GSTheme *theTheme = nil;
|
||
static NSMutableDictionary *themes = nil;
|
||
static NSNull *null = nil;
|
||
|
||
+ (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];
|
||
[[NSNotificationCenter defaultCenter]
|
||
addObserver: self
|
||
selector: @selector(defaultsDidChange:)
|
||
name: NSUserDefaultsDidChangeNotification
|
||
object: nil];
|
||
}
|
||
if (null == nil)
|
||
{
|
||
null = RETAIN([NSNull null]);
|
||
}
|
||
if (defaultTheme == nil)
|
||
{
|
||
NSBundle *aBundle = [NSBundle bundleForClass: self];
|
||
|
||
defaultTheme = [[self alloc] initWithBundle: aBundle];
|
||
ASSIGN(theTheme, defaultTheme);
|
||
}
|
||
/* 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;
|
||
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)
|
||
{
|
||
[theTheme deactivate];
|
||
ASSIGN (theTheme, theme);
|
||
[theTheme activate];
|
||
}
|
||
ASSIGN(currentThemeName, [theTheme name]);
|
||
}
|
||
|
||
+ (GSTheme*) theme
|
||
{
|
||
return theTheme;
|
||
}
|
||
|
||
- (void) activate
|
||
{
|
||
NSUserDefaults *defs;
|
||
NSMutableDictionary *userInfo;
|
||
NSMutableArray *searchList;
|
||
NSArray *imagePaths;
|
||
NSEnumerator *enumerator;
|
||
NSString *imagePath;
|
||
NSArray *imageTypes;
|
||
NSString *colorsPath;
|
||
NSDictionary *infoDict;
|
||
NSWindow *window;
|
||
|
||
userInfo = [NSMutableDictionary dictionary];
|
||
colorsPath = [_bundle pathForResource: @"ThemeColors" ofType: @"clr"];
|
||
if (colorsPath != nil)
|
||
{
|
||
NSColorList *list = nil;
|
||
|
||
list = [[NSColorList alloc] initWithName: @"System"
|
||
fromFile: colorsPath];
|
||
if (list != nil)
|
||
{
|
||
[userInfo setObject: list forKey: @"Colors"];
|
||
RELEASE(list);
|
||
}
|
||
}
|
||
|
||
/*
|
||
* We step through all the bundle image resources and load them in
|
||
* to memory, setting their names so that they are visible to
|
||
* [NSImage+imageNamed:] and storing them in our local array.
|
||
*/
|
||
imageTypes = [NSImage imageFileTypes];
|
||
imagePaths = [_bundle pathsForResourcesOfType: nil
|
||
inDirectory: @"ThemeImages"];
|
||
enumerator = [imagePaths objectEnumerator];
|
||
while ((imagePath = [enumerator nextObject]) != nil)
|
||
{
|
||
NSString *ext = [imagePath pathExtension];
|
||
|
||
if (ext != nil && [imageTypes containsObject: ext] == YES)
|
||
{
|
||
NSImage *image;
|
||
NSString *imageName;
|
||
|
||
imageName = [imagePath lastPathComponent];
|
||
imageName = [imageName stringByDeletingPathExtension];
|
||
|
||
image = [_images objectForKey: imageName];
|
||
if (image == nil)
|
||
{
|
||
image = [[NSImage alloc] initWithContentsOfFile: imagePath];
|
||
if (image != nil)
|
||
{
|
||
[_images setObject: image forKey: imageName];
|
||
RELEASE(image);
|
||
}
|
||
}
|
||
|
||
/* We try to ensure that our new image can be found by name.
|
||
*/
|
||
if (image != nil && [[image name] isEqualToString: imageName] == NO)
|
||
{
|
||
if ([image setName: imageName] == NO)
|
||
{
|
||
NSImage *old;
|
||
|
||
/* Couldn't set image name ... presumably already
|
||
* in use ... so we remove the name from the old
|
||
* image and try again.
|
||
*/
|
||
old = [NSImage imageNamed: imageName];
|
||
[old setName: nil];
|
||
[image setName: imageName];
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/*
|
||
* We could cache tile info here, but it's probably better for the
|
||
* tilesNamed:cache: method to do it lazily.
|
||
*/
|
||
|
||
/*
|
||
* 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)
|
||
{
|
||
unsigned 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);
|
||
|
||
/*
|
||
* Tell all other classes that new theme information is present.
|
||
*/
|
||
[[NSNotificationCenter defaultCenter]
|
||
postNotificationName: GSThemeDidActivateNotification
|
||
object: self
|
||
userInfo: userInfo];
|
||
|
||
/*
|
||
* Reset main menu to change between styles if necessary
|
||
*/
|
||
[[NSApp mainMenu] setMain: YES];
|
||
|
||
/*
|
||
* Mark all windows as needing redisplaying to thos 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;
|
||
}
|
||
|
||
- (void) deactivate
|
||
{
|
||
NSEnumerator *enumerator;
|
||
NSImage *image;
|
||
|
||
/*
|
||
* Remove all cached bundle images from both NSImage's name dictionary
|
||
* and our cache dictionary, so that we can be sure we reload afresh
|
||
* when re-activated (in case the images on disk changed ... eg by a
|
||
* theme editor modifying the theme).
|
||
*/
|
||
enumerator = [_images objectEnumerator];
|
||
while ((image = [enumerator nextObject]) != nil)
|
||
{
|
||
[image setName: nil];
|
||
}
|
||
[_images removeAllObjects];
|
||
|
||
/* Tell everything that we have become inactive.
|
||
*/
|
||
[[NSNotificationCenter defaultCenter]
|
||
postNotificationName: GSThemeDidDeactivateNotification
|
||
object: self
|
||
userInfo: nil];
|
||
}
|
||
|
||
- (void) dealloc
|
||
{
|
||
RELEASE(_bundle);
|
||
RELEASE(_images);
|
||
RELEASE(_tiles);
|
||
RELEASE(_icon);
|
||
[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 = [[NSImage alloc] initWithContentsOfFile: path];
|
||
}
|
||
}
|
||
if (_icon == nil)
|
||
{
|
||
_icon = RETAIN([NSImage imageNamed: @"GNUstep"]);
|
||
}
|
||
}
|
||
return _icon;
|
||
}
|
||
|
||
- (id) initWithBundle: (NSBundle*)bundle
|
||
{
|
||
ASSIGN(_bundle, bundle);
|
||
_images = [NSMutableDictionary new];
|
||
_tiles = [NSMutableDictionary new];
|
||
return self;
|
||
}
|
||
|
||
- (NSDictionary*) infoDictionary
|
||
{
|
||
return [_bundle infoDictionary];
|
||
}
|
||
|
||
- (NSString*) name
|
||
{
|
||
if (self == defaultTheme)
|
||
{
|
||
return @"GNUstep";
|
||
}
|
||
return
|
||
[[[_bundle bundlePath] lastPathComponent] stringByDeletingPathExtension];
|
||
}
|
||
|
||
- (NSWindow*) themeInspector
|
||
{
|
||
return [GSThemeInspector sharedThemeInspector];
|
||
}
|
||
|
||
- (GSDrawTiles*) tilesNamed: (NSString*)aName cache: (BOOL)useCache
|
||
{
|
||
GSDrawTiles *tiles;
|
||
|
||
tiles = (useCache == YES) ? [_tiles objectForKey: aName] : nil;
|
||
if (tiles == nil)
|
||
{
|
||
NSDictionary *info;
|
||
NSImage *image;
|
||
|
||
/* 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: aName];
|
||
if ([info isKindOfClass: [NSDictionary class]] == YES)
|
||
{
|
||
float x;
|
||
float y;
|
||
NSString *path;
|
||
NSString *file;
|
||
NSString *ext;
|
||
|
||
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 = [[NSImage alloc] initWithContentsOfFile: path];
|
||
if (image != nil)
|
||
{
|
||
tiles = [[GSDrawTiles alloc] initWithImage: image
|
||
horizontal: x
|
||
vertical: y];
|
||
RELEASE(image);
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
NSArray *imageTypes;
|
||
NSString *imagePath;
|
||
unsigned count;
|
||
|
||
imageTypes = [NSImage imageFileTypes];
|
||
for (count = 0; image == nil && count < [imageTypes count]; count++)
|
||
{
|
||
NSString *ext = [imageTypes objectAtIndex: count];
|
||
|
||
imagePath = [_bundle pathForResource: aName
|
||
ofType: ext
|
||
inDirectory: @"ThemeTiles"];
|
||
if (imagePath != nil)
|
||
{
|
||
image = [[NSImage alloc] initWithContentsOfFile: imagePath];
|
||
if (image != nil)
|
||
{
|
||
tiles = [[GSDrawTiles alloc] initWithImage: image];
|
||
RELEASE(image);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
if (tiles == nil)
|
||
{
|
||
[_tiles setObject: null forKey: aName];
|
||
}
|
||
else
|
||
{
|
||
[_tiles setObject: tiles forKey: aName];
|
||
RELEASE(tiles);
|
||
}
|
||
}
|
||
if (tiles == (id)null)
|
||
{
|
||
tiles = nil;
|
||
}
|
||
return tiles;
|
||
}
|
||
|
||
@end
|
||
|
||
|
||
@implementation GSTheme (Drawing)
|
||
|
||
- (void) drawButton: (NSRect)frame
|
||
in: (NSButtonCell*)cell
|
||
view: (NSView*)view
|
||
style: (int)style
|
||
state: (GSThemeControlState)state
|
||
{
|
||
GSDrawTiles *tiles = nil;
|
||
NSColor *color = nil;
|
||
|
||
if (state == GSThemeNormalState)
|
||
{
|
||
tiles = [self tilesNamed: @"NSButtonNormal" cache: YES];
|
||
color = [NSColor controlBackgroundColor];
|
||
}
|
||
else if (state == GSThemeHighlightedState)
|
||
{
|
||
tiles = [self tilesNamed: @"NSButtonHighlighted" cache: YES];
|
||
color = [NSColor selectedControlColor];
|
||
}
|
||
else if (state == GSThemeSelectedState)
|
||
{
|
||
tiles = [self tilesNamed: @"NSButtonPushed" cache: YES];
|
||
color = [NSColor selectedControlColor];
|
||
}
|
||
|
||
if (tiles == nil)
|
||
{
|
||
switch (style)
|
||
{
|
||
case NSRoundRectBezelStyle:
|
||
case NSTexturedRoundBezelStyle:
|
||
case NSRoundedBezelStyle:
|
||
[self drawRoundBezel: frame withColor: color];
|
||
break;
|
||
case NSTexturedSquareBezelStyle:
|
||
frame = NSInsetRect(frame, 0, 1);
|
||
case NSSmallSquareBezelStyle:
|
||
case NSRegularSquareBezelStyle:
|
||
case NSShadowlessSquareBezelStyle:
|
||
[color set];
|
||
NSRectFill(frame);
|
||
[[NSColor controlShadowColor] set];
|
||
NSFrameRectWithWidth(frame, 1);
|
||
break;
|
||
case NSThickSquareBezelStyle:
|
||
[color set];
|
||
NSRectFill(frame);
|
||
[[NSColor controlShadowColor] set];
|
||
NSFrameRectWithWidth(frame, 1.5);
|
||
break;
|
||
case NSThickerSquareBezelStyle:
|
||
[color set];
|
||
NSRectFill(frame);
|
||
[[NSColor controlShadowColor] set];
|
||
NSFrameRectWithWidth(frame, 2);
|
||
break;
|
||
case NSCircularBezelStyle:
|
||
frame = NSInsetRect(frame, 3, 3);
|
||
case NSHelpButtonBezelStyle:
|
||
[self drawCircularBezel: frame withColor: color];
|
||
break;
|
||
case NSDisclosureBezelStyle:
|
||
case NSRoundedDisclosureBezelStyle:
|
||
case NSRecessedBezelStyle:
|
||
// FIXME
|
||
break;
|
||
default:
|
||
[color set];
|
||
NSRectFill(frame);
|
||
|
||
if (state == GSThemeNormalState || state == GSThemeHighlightedState)
|
||
{
|
||
[self drawButton: frame withClip: NSZeroRect];
|
||
}
|
||
else if (state == GSThemeSelectedState)
|
||
{
|
||
[self drawGrayBezel: frame withClip: NSZeroRect];
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
/* Use tiles to draw button border with central part filled with color
|
||
*/
|
||
[self fillRect: frame
|
||
withTiles: tiles
|
||
background: color
|
||
fillStyle: GSThemeFillStyleNone];
|
||
}
|
||
}
|
||
|
||
- (NSSize) buttonBorderForStyle: (int)style
|
||
state: (GSThemeControlState)state
|
||
{
|
||
GSDrawTiles *tiles = nil;
|
||
|
||
if (state == GSThemeNormalState)
|
||
{
|
||
tiles = [self tilesNamed: @"NSButtonNormal" cache: YES];
|
||
}
|
||
else if (state == GSThemeHighlightedState)
|
||
{
|
||
tiles = [self tilesNamed: @"NSButtonHighlighted" cache: YES];
|
||
}
|
||
else if (state == GSThemeSelectedState)
|
||
{
|
||
tiles = [self tilesNamed: @"NSButtonPushed" cache: YES];
|
||
}
|
||
|
||
if (tiles == nil)
|
||
{
|
||
switch (style)
|
||
{
|
||
case NSRoundRectBezelStyle:
|
||
case NSTexturedRoundBezelStyle:
|
||
case NSRoundedBezelStyle:
|
||
return NSMakeSize(5, 5);
|
||
case NSTexturedSquareBezelStyle:
|
||
return NSMakeSize(3, 3);
|
||
case NSSmallSquareBezelStyle:
|
||
case NSRegularSquareBezelStyle:
|
||
case NSShadowlessSquareBezelStyle:
|
||
return NSMakeSize(2, 2);
|
||
case NSThickSquareBezelStyle:
|
||
return NSMakeSize(3, 3);
|
||
case NSThickerSquareBezelStyle:
|
||
return NSMakeSize(4, 4);
|
||
case NSCircularBezelStyle:
|
||
return NSMakeSize(5, 5);
|
||
case NSHelpButtonBezelStyle:
|
||
return NSMakeSize(2, 2);
|
||
case NSDisclosureBezelStyle:
|
||
case NSRoundedDisclosureBezelStyle:
|
||
case NSRecessedBezelStyle:
|
||
// FIXME
|
||
return NSMakeSize(0, 0);
|
||
default:
|
||
return NSMakeSize(3, 3);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
NSSize cls = tiles->rects[TileCL].size;
|
||
NSSize bms = tiles->rects[TileBM].size;
|
||
|
||
return NSMakeSize(cls.width, bms.height);
|
||
}
|
||
}
|
||
|
||
- (void) drawFocusFrame: (NSRect) frame view: (NSView*) view
|
||
{
|
||
NSDottedFrameRect(frame);
|
||
}
|
||
|
||
- (void) drawWindowBackground: (NSRect) frame view: (NSView*) view
|
||
{
|
||
NSColor *c;
|
||
|
||
c = [[view window] backgroundColor];
|
||
[c set];
|
||
NSRectFill (frame);
|
||
}
|
||
|
||
@end
|
||
|
||
|
||
|
||
@implementation GSTheme (MidLevelDrawing)
|
||
|
||
- (NSRect) drawButton: (NSRect)border withClip: (NSRect)clip
|
||
{
|
||
NSRectEdge up_sides[] = {NSMaxXEdge, NSMinYEdge,
|
||
NSMinXEdge, NSMaxYEdge,
|
||
NSMaxXEdge, NSMinYEdge};
|
||
NSRectEdge dn_sides[] = {NSMaxXEdge, NSMaxYEdge,
|
||
NSMinXEdge, NSMinYEdge,
|
||
NSMaxXEdge, NSMaxYEdge};
|
||
// These names are role names not the actual colours
|
||
NSColor *black = [NSColor controlDarkShadowColor];
|
||
NSColor *dark = [NSColor controlShadowColor];
|
||
NSColor *white = [NSColor controlLightHighlightColor];
|
||
NSColor *colors[] = {black, black, white, white, dark, dark};
|
||
|
||
if ([[NSView focusView] isFlipped] == YES)
|
||
{
|
||
return NSDrawColorTiledRects(border, clip, dn_sides, colors, 6);
|
||
}
|
||
else
|
||
{
|
||
return NSDrawColorTiledRects(border, clip, up_sides, colors, 6);
|
||
}
|
||
}
|
||
|
||
- (NSRect) drawDarkBezel: (NSRect)border withClip: (NSRect)clip
|
||
{
|
||
NSRectEdge up_sides[] = {NSMaxXEdge, NSMinYEdge, NSMinXEdge, NSMaxYEdge,
|
||
NSMinXEdge, NSMaxYEdge, NSMaxXEdge, NSMinYEdge};
|
||
NSRectEdge dn_sides[] = {NSMaxXEdge, NSMaxYEdge, NSMinXEdge, NSMinYEdge,
|
||
NSMinXEdge, NSMinYEdge, NSMaxXEdge, NSMaxYEdge};
|
||
// These names are role names not the actual colours
|
||
NSColor *black = [NSColor controlDarkShadowColor];
|
||
NSColor *dark = [NSColor controlShadowColor];
|
||
NSColor *light = [NSColor controlColor];
|
||
NSColor *white = [NSColor controlLightHighlightColor];
|
||
NSColor *colors[] = {white, white, dark, dark, black, black, light, light};
|
||
NSRect rect;
|
||
|
||
if ([[NSView focusView] isFlipped] == YES)
|
||
{
|
||
rect = NSDrawColorTiledRects(border, clip, dn_sides, colors, 8);
|
||
|
||
[dark set];
|
||
PSrectfill(NSMinX(border) + 1., NSMinY(border) - 2., 1., 1.);
|
||
PSrectfill(NSMaxX(border) - 2., NSMaxY(border) + 1., 1., 1.);
|
||
}
|
||
else
|
||
{
|
||
rect = NSDrawColorTiledRects(border, clip, up_sides, colors, 8);
|
||
|
||
[dark set];
|
||
PSrectfill(NSMinX(border) + 1., NSMinY(border) + 1., 1., 1.);
|
||
PSrectfill(NSMaxX(border) - 2., NSMaxY(border) - 2., 1., 1.);
|
||
}
|
||
return rect;
|
||
}
|
||
|
||
- (NSRect) drawDarkButton: (NSRect)border withClip: (NSRect)clip
|
||
{
|
||
NSRectEdge up_sides[] = {NSMaxXEdge, NSMinYEdge,
|
||
NSMinXEdge, NSMaxYEdge};
|
||
NSRectEdge dn_sides[] = {NSMaxXEdge, NSMaxYEdge,
|
||
NSMinXEdge, NSMinYEdge};
|
||
// These names are role names not the actual colours
|
||
NSColor *black = [NSColor controlDarkShadowColor];
|
||
NSColor *white = [NSColor controlHighlightColor];
|
||
NSColor *colors[] = {black, black, white, white};
|
||
|
||
if ([[NSView focusView] isFlipped] == YES)
|
||
{
|
||
return NSDrawColorTiledRects(border, clip, dn_sides, colors, 4);
|
||
}
|
||
else
|
||
{
|
||
return NSDrawColorTiledRects(border, clip, up_sides, colors, 4);
|
||
}
|
||
}
|
||
|
||
- (NSRect) drawFramePhoto: (NSRect)border withClip: (NSRect)clip
|
||
{
|
||
NSRectEdge up_sides[] = {NSMaxXEdge, NSMinYEdge,
|
||
NSMinXEdge, NSMaxYEdge,
|
||
NSMaxXEdge, NSMinYEdge};
|
||
NSRectEdge dn_sides[] = {NSMaxXEdge, NSMaxYEdge,
|
||
NSMinXEdge, NSMinYEdge,
|
||
NSMaxXEdge, NSMaxYEdge};
|
||
// These names are role names not the actual colours
|
||
NSColor *black = [NSColor controlDarkShadowColor];
|
||
NSColor *dark = [NSColor controlShadowColor];
|
||
NSColor *colors[] = {dark, dark, dark, dark,
|
||
black,black};
|
||
|
||
if ([[NSView focusView] isFlipped] == YES)
|
||
{
|
||
return NSDrawColorTiledRects(border, clip, dn_sides, colors, 6);
|
||
}
|
||
else
|
||
{
|
||
return NSDrawColorTiledRects(border, clip, up_sides, colors, 6);
|
||
}
|
||
}
|
||
|
||
- (NSRect) drawGradientBorder: (NSGradientType)gradientType
|
||
inRect: (NSRect)border
|
||
withClip: (NSRect)clip
|
||
{
|
||
NSRectEdge up_sides[] = {NSMaxXEdge, NSMinYEdge,
|
||
NSMinXEdge, NSMaxYEdge};
|
||
NSRectEdge dn_sides[] = {NSMaxXEdge, NSMaxYEdge,
|
||
NSMinXEdge, NSMinYEdge};
|
||
NSColor *black = [NSColor controlDarkShadowColor];
|
||
NSColor *dark = [NSColor controlShadowColor];
|
||
NSColor *light = [NSColor controlColor];
|
||
NSColor **colors;
|
||
NSColor *concaveWeak[] = {dark, dark, light, light};
|
||
NSColor *concaveStrong[] = {black, black, light, light};
|
||
NSColor *convexWeak[] = {light, light, dark, dark};
|
||
NSColor *convexStrong[] = {light, light, black, black};
|
||
NSRect rect;
|
||
|
||
switch (gradientType)
|
||
{
|
||
case NSGradientConcaveWeak:
|
||
colors = concaveWeak;
|
||
break;
|
||
case NSGradientConcaveStrong:
|
||
colors = concaveStrong;
|
||
break;
|
||
case NSGradientConvexWeak:
|
||
colors = convexWeak;
|
||
break;
|
||
case NSGradientConvexStrong:
|
||
colors = convexStrong;
|
||
break;
|
||
case NSGradientNone:
|
||
default:
|
||
return border;
|
||
}
|
||
|
||
if ([[NSView focusView] isFlipped] == YES)
|
||
{
|
||
rect = NSDrawColorTiledRects(border, clip, dn_sides, colors, 4);
|
||
}
|
||
else
|
||
{
|
||
rect = NSDrawColorTiledRects(border, clip, up_sides, colors, 4);
|
||
}
|
||
|
||
return rect;
|
||
}
|
||
|
||
- (NSRect) drawGrayBezel: (NSRect)border withClip: (NSRect)clip
|
||
{
|
||
NSRectEdge up_sides[] = {NSMaxXEdge, NSMinYEdge, NSMinXEdge, NSMaxYEdge,
|
||
NSMaxXEdge, NSMinYEdge, NSMinXEdge, NSMaxYEdge};
|
||
NSRectEdge dn_sides[] = {NSMaxXEdge, NSMaxYEdge, NSMinXEdge, NSMinYEdge,
|
||
NSMaxXEdge, NSMaxYEdge, NSMinXEdge, NSMinYEdge};
|
||
// These names are role names not the actual colours
|
||
NSColor *black = [NSColor controlDarkShadowColor];
|
||
NSColor *dark = [NSColor controlShadowColor];
|
||
NSColor *light = [NSColor controlColor];
|
||
NSColor *white = [NSColor controlLightHighlightColor];
|
||
NSColor *colors[] = {white, white, dark, dark,
|
||
light, light, black, black};
|
||
NSRect rect;
|
||
|
||
if ([[NSView focusView] isFlipped] == YES)
|
||
{
|
||
rect = NSDrawColorTiledRects(border, clip, dn_sides, colors, 8);
|
||
[dark set];
|
||
PSrectfill(NSMinX(border) + 1., NSMaxY(border) - 2., 1., 1.);
|
||
PSrectfill(NSMaxX(border) - 2., NSMinY(border) + 1., 1., 1.);
|
||
}
|
||
else
|
||
{
|
||
rect = NSDrawColorTiledRects(border, clip, up_sides, colors, 8);
|
||
[dark set];
|
||
PSrectfill(NSMinX(border) + 1., NSMinY(border) + 1., 1., 1.);
|
||
PSrectfill(NSMaxX(border) - 2., NSMaxY(border) - 2., 1., 1.);
|
||
}
|
||
return rect;
|
||
}
|
||
|
||
- (NSRect) drawGroove: (NSRect)border withClip: (NSRect)clip
|
||
{
|
||
// go clockwise from the top twice -- makes the groove come out right
|
||
NSRectEdge up_sides[] = {NSMaxYEdge, NSMaxXEdge, NSMinYEdge, NSMinXEdge,
|
||
NSMaxYEdge, NSMaxXEdge, NSMinYEdge, NSMinXEdge};
|
||
NSRectEdge dn_sides[] = {NSMinYEdge, NSMaxXEdge, NSMaxYEdge, NSMinXEdge,
|
||
NSMinYEdge, NSMaxXEdge, NSMaxYEdge, NSMinXEdge};
|
||
// These names are role names not the actual colours
|
||
NSColor *dark = [NSColor controlShadowColor];
|
||
NSColor *white = [NSColor controlLightHighlightColor];
|
||
NSColor *colors[] = {dark, white, white, dark,
|
||
white, dark, dark, white};
|
||
|
||
if ([[NSView focusView] isFlipped] == YES)
|
||
{
|
||
return NSDrawColorTiledRects(border, clip, dn_sides, colors, 8);
|
||
}
|
||
else
|
||
{
|
||
return NSDrawColorTiledRects(border, clip, up_sides, colors, 8);
|
||
}
|
||
}
|
||
|
||
- (NSRect) drawLightBezel: (NSRect)border withClip: (NSRect)clip
|
||
{
|
||
NSRectEdge up_sides[] = {NSMaxXEdge, NSMinYEdge, NSMinXEdge, NSMaxYEdge,
|
||
NSMaxXEdge, NSMinYEdge, NSMinXEdge, NSMaxYEdge};
|
||
NSRectEdge dn_sides[] = {NSMaxXEdge, NSMaxYEdge, NSMinXEdge, NSMinYEdge,
|
||
NSMaxXEdge, NSMaxYEdge, NSMinXEdge, NSMinYEdge};
|
||
// These names are role names not the actual colours
|
||
NSColor *dark = [NSColor controlShadowColor];
|
||
NSColor *light = [NSColor controlColor];
|
||
NSColor *white = [NSColor controlLightHighlightColor];
|
||
NSColor *colors[] = {white, white, dark, dark,
|
||
light, light, dark, dark};
|
||
|
||
if ([[NSView focusView] isFlipped] == YES)
|
||
{
|
||
return NSDrawColorTiledRects(border, clip, dn_sides, colors, 8);
|
||
}
|
||
else
|
||
{
|
||
return NSDrawColorTiledRects(border, clip, up_sides, colors, 8);
|
||
}
|
||
}
|
||
|
||
- (NSRect) drawWhiteBezel: (NSRect)border withClip: (NSRect)clip
|
||
{
|
||
NSRectEdge up_sides[] = {NSMaxYEdge, NSMaxXEdge, NSMinYEdge, NSMinXEdge,
|
||
NSMaxYEdge, NSMaxXEdge, NSMinYEdge, NSMinXEdge};
|
||
NSRectEdge dn_sides[] = {NSMinYEdge, NSMaxXEdge, NSMaxYEdge, NSMinXEdge,
|
||
NSMinYEdge, NSMaxXEdge, NSMaxYEdge, NSMinXEdge};
|
||
// These names are role names not the actual colours
|
||
NSColor *dark = [NSColor controlShadowColor];
|
||
NSColor *light = [NSColor controlColor];
|
||
NSColor *white = [NSColor controlLightHighlightColor];
|
||
NSColor *colors[] = {dark, white, white, dark,
|
||
dark, light, light, dark};
|
||
|
||
if ([[NSView focusView] isFlipped] == YES)
|
||
{
|
||
return NSDrawColorTiledRects(border, clip, dn_sides, colors, 8);
|
||
}
|
||
else
|
||
{
|
||
return NSDrawColorTiledRects(border, clip, up_sides, colors, 8);
|
||
}
|
||
}
|
||
|
||
- (void) drawRoundBezel: (NSRect)cellFrame withColor: (NSColor*)backgroundColor
|
||
{
|
||
NSBezierPath *p = [NSBezierPath bezierPath];
|
||
NSPoint point;
|
||
float radius;
|
||
|
||
// make smaller than enclosing frame
|
||
cellFrame = NSInsetRect(cellFrame, 4, floor(cellFrame.size.height * 0.1875));
|
||
radius = cellFrame.size.height / 2.0;
|
||
point = cellFrame.origin;
|
||
point.x += radius;
|
||
point.y += radius;
|
||
// left half-circle
|
||
[p appendBezierPathWithArcWithCenter: point radius: radius startAngle: 90.0 endAngle: 270.0];
|
||
|
||
// line to first point and right halfcircle
|
||
point.x += cellFrame.size.width - cellFrame.size.height;
|
||
[p appendBezierPathWithArcWithCenter: point radius: radius startAngle: 270.0 endAngle: 90.0];
|
||
[p closePath];
|
||
|
||
// fill with background color
|
||
[backgroundColor set];
|
||
[p fill];
|
||
|
||
// and stroke rounded button
|
||
[[NSColor shadowColor] set];
|
||
[p stroke];
|
||
}
|
||
|
||
- (void) drawCircularBezel: (NSRect)cellFrame withColor: (NSColor*)backgroundColor
|
||
{
|
||
// make smaller so that it does not touch frame
|
||
NSBezierPath *oval = [NSBezierPath bezierPathWithOvalInRect: NSInsetRect(cellFrame, 1, 1)];
|
||
|
||
// fill oval with background color
|
||
[backgroundColor set];
|
||
[oval fill];
|
||
|
||
// and stroke rounded button
|
||
[[NSColor shadowColor] set];
|
||
[oval stroke];
|
||
}
|
||
|
||
@end
|
||
|
||
|
||
|
||
@implementation GSTheme (LowLevelDrawing)
|
||
|
||
- (void) fillHorizontalRect: (NSRect)rect
|
||
withImage: (NSImage*)image
|
||
fromRect: (NSRect)source
|
||
flipped: (BOOL)flipped
|
||
{
|
||
NSGraphicsContext *ctxt = GSCurrentContext();
|
||
NSBezierPath *path;
|
||
unsigned repetitions;
|
||
unsigned count;
|
||
float y;
|
||
|
||
DPSgsave (ctxt);
|
||
path = [NSBezierPath bezierPathWithRect: rect];
|
||
[path addClip];
|
||
repetitions = (rect.size.width / source.size.width) + 1;
|
||
y = rect.origin.y;
|
||
|
||
if (flipped) y = rect.origin.y + rect.size.height;
|
||
|
||
for (count = 0; count < repetitions; count++)
|
||
{
|
||
NSPoint p = NSMakePoint (rect.origin.x + count * source.size.width, y);
|
||
|
||
[image compositeToPoint: p
|
||
fromRect: source
|
||
operation: NSCompositeSourceOver];
|
||
}
|
||
DPSgrestore (ctxt);
|
||
}
|
||
|
||
- (void) fillRect: (NSRect)rect
|
||
withRepeatedImage: (NSImage*)image
|
||
fromRect: (NSRect)source
|
||
center: (BOOL)center
|
||
{
|
||
NSGraphicsContext *ctxt = GSCurrentContext ();
|
||
NSBezierPath *path;
|
||
NSSize size;
|
||
unsigned xrepetitions;
|
||
unsigned yrepetitions;
|
||
unsigned x;
|
||
unsigned y;
|
||
|
||
DPSgsave (ctxt);
|
||
path = [NSBezierPath bezierPathWithRect: rect];
|
||
[path addClip];
|
||
size = [image size];
|
||
xrepetitions = (rect.size.width / size.width) + 1;
|
||
yrepetitions = (rect.size.height / size.height) + 1;
|
||
|
||
for (x = 0; x < xrepetitions; x++)
|
||
{
|
||
for (y = 0; y < yrepetitions; y++)
|
||
{
|
||
NSPoint p;
|
||
|
||
p = NSMakePoint (rect.origin.x + x * size.width,
|
||
rect.origin.y + y * size.height);
|
||
[image compositeToPoint: p
|
||
fromRect: source
|
||
operation: NSCompositeSourceOver];
|
||
}
|
||
}
|
||
DPSgrestore (ctxt);
|
||
}
|
||
|
||
- (NSRect) fillRect: (NSRect)rect
|
||
withTiles: (GSDrawTiles*)tiles
|
||
background: (NSColor*)color
|
||
fillStyle: (GSThemeFillStyle)style
|
||
{
|
||
NSGraphicsContext *ctxt = GSCurrentContext();
|
||
NSSize tls = tiles->rects[TileTL].size;
|
||
NSSize tms = tiles->rects[TileTM].size;
|
||
NSSize trs = tiles->rects[TileTR].size;
|
||
NSSize cls = tiles->rects[TileCL].size;
|
||
NSSize crs = tiles->rects[TileCR].size;
|
||
NSSize bls = tiles->rects[TileBL].size;
|
||
NSSize bms = tiles->rects[TileBM].size;
|
||
NSSize brs = tiles->rects[TileBR].size;
|
||
NSRect inFill;
|
||
BOOL flipped = [[ctxt focusView] isFlipped];
|
||
|
||
if (color == nil)
|
||
{
|
||
[[NSColor redColor] set];
|
||
}
|
||
else
|
||
{
|
||
[color set];
|
||
}
|
||
NSRectFill(rect);
|
||
|
||
if (style == GSThemeFillStyleMatrix)
|
||
{
|
||
NSRect grid;
|
||
float x;
|
||
float y;
|
||
float space = 3.0;
|
||
float scale;
|
||
|
||
inFill = NSZeroRect;
|
||
if (tiles->images[TileTM] == nil)
|
||
{
|
||
grid.size.width = (tiles->rects[TileTL].size.width
|
||
+ tiles->rects[TileTR].size.width
|
||
+ space * 3.0);
|
||
}
|
||
else
|
||
{
|
||
grid.size.width = (tiles->rects[TileTL].size.width
|
||
+ tiles->rects[TileTM].size.width
|
||
+ tiles->rects[TileTR].size.width
|
||
+ space * 4.0);
|
||
}
|
||
scale = floor(rect.size.width / grid.size.width);
|
||
|
||
if (tiles->images[TileCL] == nil)
|
||
{
|
||
grid.size.height = (tiles->rects[TileTL].size.height
|
||
+ tiles->rects[TileBL].size.height
|
||
+ space * 3.0);
|
||
}
|
||
else
|
||
{
|
||
grid.size.height = (tiles->rects[TileTL].size.height
|
||
+ tiles->rects[TileCL].size.height
|
||
+ tiles->rects[TileBL].size.height
|
||
+ space * 4.0);
|
||
}
|
||
if ((rect.size.height / grid.size.height) < scale)
|
||
{
|
||
scale = floor(rect.size.height / grid.size.height);
|
||
}
|
||
|
||
if (scale > 1)
|
||
{
|
||
/* We can scale up by an integer number of pixels and still
|
||
* fit in the rectangle.
|
||
*/
|
||
grid.size.width *= scale;
|
||
grid.size.height *= scale;
|
||
space *= scale;
|
||
tiles = AUTORELEASE([tiles copy]);
|
||
[tiles scaleUp: (int)scale];
|
||
}
|
||
|
||
grid.origin.x = rect.origin.x + (rect.size.width - grid.size.width) / 2;
|
||
x = grid.origin.x;
|
||
if (flipped)
|
||
{
|
||
grid.origin.y
|
||
= NSMaxY(rect) - (rect.size.height - grid.size.height) / 2;
|
||
y = NSMaxY(grid);
|
||
}
|
||
else
|
||
{
|
||
grid.origin.y
|
||
= rect.origin.y + (rect.size.height - grid.size.height) / 2;
|
||
y = grid.origin.y;
|
||
}
|
||
|
||
/* Draw bottom row
|
||
*/
|
||
if (flipped)
|
||
{
|
||
y -= (tiles->rects[TileBL].size.height + space);
|
||
}
|
||
else
|
||
{
|
||
y += space;
|
||
}
|
||
[tiles->images[TileBL] compositeToPoint: NSMakePoint(x, y)
|
||
fromRect: tiles->rects[TileBL]
|
||
operation: NSCompositeSourceOver];
|
||
x += tiles->rects[TileBL].size.width + space;
|
||
if (tiles->images[TileBM] != nil)
|
||
{
|
||
[tiles->images[TileBM] compositeToPoint: NSMakePoint(x, y)
|
||
fromRect: tiles->rects[TileBM]
|
||
operation: NSCompositeSourceOver];
|
||
x += tiles->rects[TileBM].size.width + space;
|
||
}
|
||
[tiles->images[TileBR] compositeToPoint: NSMakePoint(x, y)
|
||
fromRect: tiles->rects[TileBR]
|
||
operation: NSCompositeSourceOver];
|
||
if (!flipped)
|
||
{
|
||
y += tiles->rects[TileBL].size.height;
|
||
}
|
||
|
||
if (tiles->images[TileCL] != nil)
|
||
{
|
||
/* Draw middle row
|
||
*/
|
||
x = grid.origin.x;
|
||
if (flipped)
|
||
{
|
||
y -= (tiles->rects[TileCL].size.height + space);
|
||
}
|
||
else
|
||
{
|
||
y += space;
|
||
}
|
||
[tiles->images[TileCL] compositeToPoint: NSMakePoint(x, y)
|
||
fromRect: tiles->rects[TileCL]
|
||
operation: NSCompositeSourceOver];
|
||
x += tiles->rects[TileCL].size.width + space;
|
||
if (tiles->images[TileCM] != nil)
|
||
{
|
||
[tiles->images[TileCM] compositeToPoint: NSMakePoint(x, y)
|
||
fromRect: tiles->rects[TileCM]
|
||
operation: NSCompositeSourceOver];
|
||
x += tiles->rects[TileCM].size.width + space;
|
||
}
|
||
[tiles->images[TileCR] compositeToPoint: NSMakePoint(x, y)
|
||
fromRect: tiles->rects[TileCR]
|
||
operation: NSCompositeSourceOver];
|
||
if (!flipped)
|
||
{
|
||
y += tiles->rects[TileCL].size.height;
|
||
}
|
||
}
|
||
|
||
/* Draw top row
|
||
*/
|
||
x = grid.origin.x;
|
||
if (flipped)
|
||
{
|
||
y -= (tiles->rects[TileTL].size.height + space);
|
||
}
|
||
else
|
||
{
|
||
y += space;
|
||
}
|
||
[tiles->images[TileTL] compositeToPoint: NSMakePoint(x, y)
|
||
fromRect: tiles->rects[TileTL]
|
||
operation: NSCompositeSourceOver];
|
||
x += tiles->rects[TileTL].size.width + space;
|
||
if (tiles->images[TileTM] != nil)
|
||
{
|
||
[tiles->images[TileTM] compositeToPoint: NSMakePoint(x, y)
|
||
fromRect: tiles->rects[TileTM]
|
||
operation: NSCompositeSourceOver];
|
||
x += tiles->rects[TileTM].size.width + space;
|
||
}
|
||
[tiles->images[TileTR] compositeToPoint: NSMakePoint(x, y)
|
||
fromRect: tiles->rects[TileTR]
|
||
operation: NSCompositeSourceOver];
|
||
}
|
||
else if (flipped)
|
||
{
|
||
[self fillHorizontalRect:
|
||
NSMakeRect (rect.origin.x + bls.width,
|
||
rect.origin.y + rect.size.height - bms.height,
|
||
rect.size.width - bls.width - brs.width,
|
||
bms.height)
|
||
withImage: tiles->images[TileBM]
|
||
fromRect: tiles->rects[TileBM]
|
||
flipped: YES];
|
||
[self fillHorizontalRect:
|
||
NSMakeRect (rect.origin.x + tls.width,
|
||
rect.origin.y,
|
||
rect.size.width - tls.width - trs.width,
|
||
tms.height)
|
||
withImage: tiles->images[TileTM]
|
||
fromRect: tiles->rects[TileTM]
|
||
flipped: YES];
|
||
[self fillVerticalRect:
|
||
NSMakeRect (rect.origin.x,
|
||
rect.origin.y + bls.height,
|
||
cls.width,
|
||
rect.size.height - bls.height - tls.height)
|
||
withImage: tiles->images[TileCL]
|
||
fromRect: tiles->rects[TileCL]
|
||
flipped: NO];
|
||
[self fillVerticalRect:
|
||
NSMakeRect (rect.origin.x + rect.size.width - crs.width,
|
||
rect.origin.y + brs.height,
|
||
crs.width,
|
||
rect.size.height - brs.height - trs.height)
|
||
withImage: tiles->images[TileCR]
|
||
fromRect: tiles->rects[TileCR]
|
||
flipped: NO];
|
||
|
||
[tiles->images[TileTL] compositeToPoint:
|
||
NSMakePoint (rect.origin.x,
|
||
rect.origin.y)
|
||
fromRect: tiles->rects[TileTL]
|
||
operation: NSCompositeSourceOver];
|
||
[tiles->images[TileTR] compositeToPoint:
|
||
NSMakePoint (rect.origin.x + rect.size.width - tls.width,
|
||
rect.origin.y)
|
||
fromRect: tiles->rects[TileTR]
|
||
operation: NSCompositeSourceOver];
|
||
[tiles->images[TileBL] compositeToPoint:
|
||
NSMakePoint (rect.origin.x,
|
||
rect.origin.y + rect.size.height - tls.height)
|
||
fromRect: tiles->rects[TileBL]
|
||
operation: NSCompositeSourceOver];
|
||
[tiles->images[TileBR] compositeToPoint:
|
||
NSMakePoint (rect.origin.x + rect.size.width - brs.width,
|
||
rect.origin.y + rect.size.height - tls.height)
|
||
fromRect: tiles->rects[TileBR]
|
||
operation: NSCompositeSourceOver];
|
||
|
||
inFill = NSMakeRect (rect.origin.x + cls.width,
|
||
rect.origin.y + bms.height,
|
||
rect.size.width - cls.width - crs.width,
|
||
rect.size.height - bms.height - tms.height);
|
||
if (style == GSThemeFillStyleCenter)
|
||
{
|
||
NSRect r = tiles->rects[TileCM];
|
||
|
||
r.origin.x
|
||
= inFill.origin.x + (inFill.size.width - r.size.width) / 2;
|
||
r.origin.y
|
||
= inFill.origin.y + (inFill.size.height - r.size.height) / 2;
|
||
r.origin.y += r.size.height; // Allow for flip of image rectangle
|
||
[tiles->images[TileCM] compositeToPoint: r.origin
|
||
fromRect: tiles->rects[TileCM]
|
||
operation: NSCompositeSourceOver];
|
||
}
|
||
else if (style == GSThemeFillStyleRepeat)
|
||
{
|
||
[self fillRect: inFill
|
||
withRepeatedImage: tiles->images[TileCM]
|
||
fromRect: tiles->rects[TileCM]
|
||
center: NO];
|
||
}
|
||
else if (style == GSThemeFillStyleScale)
|
||
{
|
||
NSImage *im = [tiles->images[TileCM] copy];
|
||
NSRect r = tiles->rects[TileCM];
|
||
NSSize s = [tiles->images[TileCM] size];
|
||
NSPoint p = inFill.origin;
|
||
float sx = inFill.size.width / r.size.width;
|
||
float sy = inFill.size.height / r.size.height;
|
||
|
||
r.size.width = inFill.size.width;
|
||
r.size.height = inFill.size.height;
|
||
r.origin.x *= sx;
|
||
r.origin.y *= sy;
|
||
s.width *= sx;
|
||
s.height *= sy;
|
||
p.y += inFill.size.height; // In flipped view
|
||
|
||
[im setScalesWhenResized: YES];
|
||
[im setSize: s];
|
||
[im compositeToPoint: p
|
||
fromRect: r
|
||
operation: NSCompositeSourceOver];
|
||
RELEASE(im);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
[self fillHorizontalRect:
|
||
NSMakeRect(
|
||
rect.origin.x + tls.width,
|
||
rect.origin.y + rect.size.height - tms.height,
|
||
rect.size.width - bls.width - brs.width,
|
||
tms.height)
|
||
withImage: tiles->images[TileTM]
|
||
fromRect: tiles->rects[TileTM]
|
||
flipped: NO];
|
||
[self fillHorizontalRect:
|
||
NSMakeRect(
|
||
rect.origin.x + bls.width,
|
||
rect.origin.y,
|
||
rect.size.width - bls.width - brs.width,
|
||
bms.height)
|
||
withImage: tiles->images[TileBM]
|
||
fromRect: tiles->rects[TileBM]
|
||
flipped: NO];
|
||
[self fillVerticalRect:
|
||
NSMakeRect(
|
||
rect.origin.x,
|
||
rect.origin.y + bls.height,
|
||
cls.width,
|
||
rect.size.height - tls.height - bls.height)
|
||
withImage: tiles->images[TileCL]
|
||
fromRect: tiles->rects[TileCL]
|
||
flipped: NO];
|
||
[self fillVerticalRect:
|
||
NSMakeRect(
|
||
rect.origin.x + rect.size.width - crs.width,
|
||
rect.origin.y + brs.height,
|
||
crs.width,
|
||
rect.size.height - trs.height - brs.height)
|
||
withImage: tiles->images[TileCR]
|
||
fromRect: tiles->rects[TileCR]
|
||
flipped: NO];
|
||
|
||
[tiles->images[TileTL] compositeToPoint:
|
||
NSMakePoint (
|
||
rect.origin.x,
|
||
rect.origin.y + rect.size.height - tls.height)
|
||
fromRect: tiles->rects[TileTL]
|
||
operation: NSCompositeSourceOver];
|
||
[tiles->images[TileTR] compositeToPoint:
|
||
NSMakePoint(
|
||
rect.origin.x + rect.size.width - trs.width,
|
||
rect.origin.y + rect.size.height - trs.height)
|
||
fromRect: tiles->rects[TileTR]
|
||
operation: NSCompositeSourceOver];
|
||
[tiles->images[TileBL] compositeToPoint:
|
||
NSMakePoint(
|
||
rect.origin.x,
|
||
rect.origin.y)
|
||
fromRect: tiles->rects[TileBL]
|
||
operation: NSCompositeSourceOver];
|
||
[tiles->images[TileBR] compositeToPoint:
|
||
NSMakePoint(
|
||
rect.origin.x + rect.size.width - brs.width,
|
||
rect.origin.y)
|
||
fromRect: tiles->rects[TileBR]
|
||
operation: NSCompositeSourceOver];
|
||
|
||
inFill = NSMakeRect (rect.origin.x +cls.width,
|
||
rect.origin.y + bms.height,
|
||
rect.size.width - cls.width - crs.width,
|
||
rect.size.height - bms.height - tms.height);
|
||
|
||
if (style == GSThemeFillStyleCenter)
|
||
{
|
||
NSRect r = tiles->rects[TileCM];
|
||
|
||
r.origin.x
|
||
= inFill.origin.x + (inFill.size.width - r.size.width) / 2;
|
||
r.origin.y
|
||
= inFill.origin.y + (inFill.size.height - r.size.height) / 2;
|
||
[tiles->images[TileCM] compositeToPoint: r.origin
|
||
fromRect: tiles->rects[TileCM]
|
||
operation: NSCompositeSourceOver];
|
||
}
|
||
else if (style == GSThemeFillStyleRepeat)
|
||
{
|
||
[self fillRect: inFill
|
||
withRepeatedImage: tiles->images[TileCM]
|
||
fromRect: tiles->rects[TileCM]
|
||
center: YES];
|
||
}
|
||
else if (style == GSThemeFillStyleScale)
|
||
{
|
||
NSImage *im = [tiles->images[TileCM] copy];
|
||
NSRect r = tiles->rects[TileCM];
|
||
NSSize s = [tiles->images[TileCM] size];
|
||
NSPoint p = inFill.origin;
|
||
float sx = inFill.size.width / r.size.width;
|
||
float sy = inFill.size.height / r.size.height;
|
||
|
||
r.size.width = inFill.size.width;
|
||
r.size.height = inFill.size.height;
|
||
r.origin.x *= sx;
|
||
r.origin.y *= sy;
|
||
s.width *= sx;
|
||
s.height *= sy;
|
||
|
||
|
||
[im setScalesWhenResized: YES];
|
||
[im setSize: s];
|
||
[im compositeToPoint: p
|
||
fromRect: r
|
||
operation: NSCompositeSourceOver];
|
||
RELEASE(im);
|
||
}
|
||
}
|
||
return inFill;
|
||
}
|
||
|
||
- (void) fillVerticalRect: (NSRect)rect
|
||
withImage: (NSImage*)image
|
||
fromRect: (NSRect)source
|
||
flipped: (BOOL)flipped
|
||
{
|
||
NSGraphicsContext *ctxt = GSCurrentContext();
|
||
NSBezierPath *path;
|
||
unsigned repetitions;
|
||
unsigned count;
|
||
NSPoint p;
|
||
|
||
DPSgsave (ctxt);
|
||
path = [NSBezierPath bezierPathWithRect: rect];
|
||
[path addClip];
|
||
repetitions = (rect.size.height / source.size.height) + 1;
|
||
|
||
if (flipped)
|
||
{
|
||
for (count = 0; count < repetitions; count++)
|
||
{
|
||
p = NSMakePoint (rect.origin.x,
|
||
rect.origin.y + rect.size.height - count * source.size.height);
|
||
[image compositeToPoint: p
|
||
fromRect: source
|
||
operation: NSCompositeSourceOver];
|
||
}
|
||
}
|
||
else
|
||
{
|
||
for (count = 0; count < repetitions; count++)
|
||
{
|
||
p = NSMakePoint (rect.origin.x,
|
||
rect.origin.y + count * source.size.height);
|
||
[image compositeToPoint: p
|
||
fromRect: source
|
||
operation: NSCompositeSourceOver];
|
||
}
|
||
}
|
||
DPSgrestore (ctxt);
|
||
}
|
||
|
||
@end
|
||
|
||
|
||
|
||
@implementation GSDrawTiles
|
||
- (id) copyWithZone: (NSZone*)zone
|
||
{
|
||
GSDrawTiles *c = (GSDrawTiles*)NSCopyObject(self, 0, zone);
|
||
unsigned i;
|
||
|
||
c->images[0] = [images[0] copy];
|
||
for (i = 1; i < 9; i++)
|
||
{
|
||
unsigned j;
|
||
|
||
for (j = 0; j < i; j++)
|
||
{
|
||
if (images[i] == images[j])
|
||
{
|
||
break;
|
||
}
|
||
}
|
||
if (j < i)
|
||
{
|
||
c->images[i] = RETAIN(c->images[j]);
|
||
}
|
||
else
|
||
{
|
||
c->images[i] = [images[i] copy];
|
||
}
|
||
}
|
||
return c;
|
||
}
|
||
|
||
- (void) dealloc
|
||
{
|
||
unsigned i;
|
||
|
||
for (i = 0; i < 9; i++)
|
||
{
|
||
RELEASE(images[i]);
|
||
}
|
||
[super dealloc];
|
||
}
|
||
|
||
/**
|
||
* Simple initialiser, assume the single image is split into nine equal tiles.
|
||
* If the image size is not divisible by three, the corners are made equal
|
||
* in size and the central parts slightly smaller.
|
||
*/
|
||
- (id) initWithImage: (NSImage*)image
|
||
{
|
||
NSSize s = [image size];
|
||
|
||
return [self initWithImage: image
|
||
horizontal: s.width / 3.0
|
||
vertical: s.height / 3.0];
|
||
}
|
||
|
||
- (id) initWithImage: (NSImage*)image horizontal: (float)x vertical: (float)y
|
||
{
|
||
unsigned i;
|
||
NSSize s = [image size];
|
||
|
||
x = floor(x);
|
||
y = floor(y);
|
||
|
||
rects[TileTL] = NSMakeRect(0.0, s.height - y, x, y);
|
||
rects[TileTM] = NSMakeRect(x, s.height - y, s.width - 2.0 * x, y);
|
||
rects[TileTR] = NSMakeRect(s.width - x, s.height - y, x, y);
|
||
rects[TileCL] = NSMakeRect(0.0, y, x, s.height - 2.0 * y);
|
||
rects[TileCM] = NSMakeRect(x, y, s.width - 2.0 * x, s.height - 2.0 * y);
|
||
rects[TileCR] = NSMakeRect(s.width - x, y, x, s.height - 2.0 * y);
|
||
rects[TileBL] = NSMakeRect(0.0, 0.0, x, y);
|
||
rects[TileBM] = NSMakeRect(x, 0.0, s.width - 2.0 * x, y);
|
||
rects[TileBR] = NSMakeRect(s.width - x, 0.0, x, y);
|
||
|
||
for (i = 0; i < 9; i++)
|
||
{
|
||
if (rects[i].origin.x < 0.0 || rects[i].origin.y < 0.0
|
||
|| rects[i].size.width <= 0.0 || rects[i].size.height <= 0.0)
|
||
{
|
||
images[i] = nil;
|
||
rects[i] = NSZeroRect;
|
||
}
|
||
else
|
||
{
|
||
images[i] = RETAIN(image);
|
||
}
|
||
}
|
||
|
||
return self;
|
||
}
|
||
|
||
- (void) scaleUp: (int)multiple
|
||
{
|
||
if (multiple > 1)
|
||
{
|
||
unsigned i;
|
||
NSSize s;
|
||
|
||
[images[0] setScalesWhenResized: YES];
|
||
s = [images[0] size];
|
||
s.width *= multiple;
|
||
s.height *= multiple;
|
||
[images[0] setSize: s];
|
||
rects[0].size.height *= multiple;
|
||
rects[0].size.width *= multiple;
|
||
rects[0].origin.x *= multiple;
|
||
rects[0].origin.y *= multiple;
|
||
for (i = 1; i < 9; i++)
|
||
{
|
||
unsigned j;
|
||
|
||
for (j = 0; j < i; j++)
|
||
{
|
||
if (images[i] == images[j])
|
||
{
|
||
break;
|
||
}
|
||
}
|
||
if (j == i)
|
||
{
|
||
[images[i] setScalesWhenResized: YES];
|
||
s = [images[i] size];
|
||
s.width *= multiple;
|
||
s.height *= multiple;
|
||
[images[i] setSize: s];
|
||
}
|
||
rects[i].size.height *= multiple;
|
||
rects[i].size.width *= multiple;
|
||
rects[i].origin.x *= multiple;
|
||
rects[i].origin.y *= multiple;
|
||
}
|
||
}
|
||
}
|
||
@end
|
||
|
||
|
||
|
||
@implementation GSThemePanel
|
||
|
||
static GSThemePanel *sharedPanel = nil;
|
||
|
||
+ (GSThemePanel*) sharedThemePanel
|
||
{
|
||
if (sharedPanel == nil)
|
||
{
|
||
sharedPanel = [self new];
|
||
}
|
||
return sharedPanel;
|
||
}
|
||
|
||
- (id) init
|
||
{
|
||
NSRect winFrame;
|
||
NSRect sideFrame;
|
||
NSRect bottomFrame;
|
||
NSRect frame;
|
||
NSView *container;
|
||
NSButtonCell *proto;
|
||
|
||
winFrame = NSMakeRect(0, 0, 367, 420);
|
||
sideFrame = NSMakeRect(0, 0, 95, 420);
|
||
bottomFrame = NSMakeRect(95, 0, 272, 32);
|
||
|
||
self = [super initWithContentRect: winFrame
|
||
styleMask: (NSTitledWindowMask | NSClosableWindowMask
|
||
| NSMiniaturizableWindowMask | NSResizableWindowMask)
|
||
backing: NSBackingStoreBuffered
|
||
defer: NO];
|
||
|
||
[self setReleasedWhenClosed: NO];
|
||
container = [self contentView];
|
||
sideView = [[NSScrollView alloc] initWithFrame: sideFrame];
|
||
[sideView setHasHorizontalScroller: NO];
|
||
[sideView setHasVerticalScroller: YES];
|
||
[sideView setBorderType: NSBezelBorder];
|
||
[sideView setAutoresizingMask: (NSViewHeightSizable | NSViewMaxXMargin)];
|
||
[container addSubview: sideView];
|
||
RELEASE(sideView);
|
||
|
||
proto = [[NSButtonCell alloc] init];
|
||
[proto setBordered: NO];
|
||
[proto setAlignment: NSCenterTextAlignment];
|
||
[proto setImagePosition: NSImageAbove];
|
||
[proto setSelectable: NO];
|
||
[proto setEditable: NO];
|
||
[matrix setPrototype: proto];
|
||
|
||
frame = [sideView frame];
|
||
frame.origin = NSZeroPoint;
|
||
matrix = [[NSMatrix alloc] initWithFrame: frame
|
||
mode: NSRadioModeMatrix
|
||
prototype: proto
|
||
numberOfRows: 1
|
||
numberOfColumns: 1];
|
||
RELEASE(proto);
|
||
[matrix setAutosizesCells: NO];
|
||
[matrix setCellSize: NSMakeSize(72,72)];
|
||
[matrix setIntercellSpacing: NSMakeSize(8,8)];
|
||
[matrix setAutoresizingMask: NSViewNotSizable];
|
||
[matrix setMode: NSRadioModeMatrix];
|
||
[matrix setAction: @selector(changeSelection:)];
|
||
[matrix setTarget: self];
|
||
|
||
[sideView setDocumentView: matrix];
|
||
RELEASE(matrix);
|
||
|
||
bottomView = [[NSView alloc] initWithFrame: bottomFrame];
|
||
[bottomView setAutoresizingMask: (NSViewWidthSizable | NSViewMaxYMargin)];
|
||
[container addSubview: bottomView];
|
||
RELEASE(bottomView);
|
||
|
||
[self setTitle: @"Themes"];
|
||
|
||
[[NSNotificationCenter defaultCenter]
|
||
addObserver: self
|
||
selector: @selector(notified:)
|
||
name: GSThemeDidActivateNotification
|
||
object: nil];
|
||
[[NSNotificationCenter defaultCenter]
|
||
addObserver: self
|
||
selector: @selector(notified:)
|
||
name: GSThemeDidDeactivateNotification
|
||
object: nil];
|
||
|
||
/* Fake a notification to set the initial value for the inspector.
|
||
*/
|
||
[self notified:
|
||
[NSNotification notificationWithName: GSThemeDidActivateNotification
|
||
object: [GSTheme theme]
|
||
userInfo: nil]];
|
||
return self;
|
||
}
|
||
|
||
- (void) changeSelection: (id)sender
|
||
{
|
||
NSButtonCell *cell = [sender selectedCell];
|
||
NSString *name = [cell title];
|
||
|
||
[GSTheme setTheme: [GSTheme loadThemeNamed: name]];
|
||
}
|
||
|
||
- (void) notified: (NSNotification*)n
|
||
{
|
||
NSView *cView;
|
||
GSThemeInspector *inspector;
|
||
|
||
inspector = (GSThemeInspector*)[[n object] themeInspector];
|
||
cView = [self contentView];
|
||
|
||
if ([[n name] isEqualToString: GSThemeDidActivateNotification] == YES)
|
||
{
|
||
NSView *iView;
|
||
NSRect frame;
|
||
NSButton *button;
|
||
NSString *dName;
|
||
|
||
/* Ask the inspector to ensure that it is up to date.
|
||
*/
|
||
[inspector update: self];
|
||
|
||
/* Move the inspector view into our window.
|
||
*/
|
||
iView = RETAIN([inspector contentView]);
|
||
[inspector setContentView: nil];
|
||
[cView addSubview: iView];
|
||
RELEASE(iView);
|
||
|
||
/* Set inspector view to fill the frame to the right of our
|
||
* scrollview and above the bottom view
|
||
*/
|
||
frame.origin.x = [sideView frame].size.width;
|
||
frame.origin.y = [bottomView frame].size.height;
|
||
frame.size = [cView frame].size;
|
||
frame.size.width -= [sideView frame].size.width;
|
||
frame.size.height -= [bottomView frame].size.height;
|
||
[iView setFrame: frame];
|
||
|
||
button = [[bottomView subviews] lastObject];
|
||
if (button == nil)
|
||
{
|
||
button = [NSButton new];
|
||
[button setTarget: self];
|
||
[button setAction: @selector(setDefault:)];
|
||
[bottomView addSubview: button];
|
||
RELEASE(button);
|
||
}
|
||
dName = [[NSUserDefaults standardUserDefaults] stringForKey: @"GSTheme"];
|
||
if ([[[n object] name] isEqual: dName] == YES)
|
||
{
|
||
[button setTitle: @"Revert default theme"];
|
||
}
|
||
else
|
||
{
|
||
[button setTitle: @"Make this the default theme"];
|
||
}
|
||
[button sizeToFit];
|
||
frame = [button frame];
|
||
frame.origin.x = ([bottomView frame].size.width - frame.size.width) / 2;
|
||
frame.origin.y = ([bottomView frame].size.height - frame.size.height) / 2;
|
||
[button setFrame: frame];
|
||
}
|
||
else
|
||
{
|
||
/* Restore the inspector content view.
|
||
*/
|
||
[inspector setContentView: [[cView subviews] lastObject]];
|
||
}
|
||
[cView setNeedsDisplay: YES];
|
||
}
|
||
|
||
- (void) setDefault: (id)sender
|
||
{
|
||
NSButton *button = (NSButton*)sender;
|
||
NSUserDefaults *defs = [NSUserDefaults standardUserDefaults];
|
||
NSString *cName;
|
||
NSString *dName;
|
||
NSRect frame;
|
||
|
||
dName = [defs stringForKey: @"GSTheme"];
|
||
cName = [[GSTheme theme] name];
|
||
|
||
if ([cName isEqual: dName] == YES)
|
||
{
|
||
[defs removeObjectForKey: @"GSTheme"];
|
||
[button setTitle: @"Make this the default theme"];
|
||
}
|
||
else
|
||
{
|
||
[defs setObject: cName forKey: @"GSTheme"];
|
||
[button setTitle: @"Revert default theme"];
|
||
}
|
||
[defs synchronize];
|
||
[button sizeToFit];
|
||
frame = [button frame];
|
||
frame.origin.x = ([bottomView frame].size.width - frame.size.width) / 2;
|
||
frame.origin.y = ([bottomView frame].size.height - frame.size.height) / 2;
|
||
[button setFrame: frame];
|
||
[bottomView setNeedsDisplay: YES];
|
||
}
|
||
|
||
- (void) update: (id)sender
|
||
{
|
||
NSArray *array;
|
||
|
||
/* Avoid [NSMutableSet set] that confuses GCC 3.3.3. It seems to confuse
|
||
* this static +(id)set method with the instance -(void)set, so it would
|
||
* refuse to compile saying
|
||
* GSTheme.m:1565: error: void value not ignored as it ought to be
|
||
*/
|
||
NSMutableSet *set = AUTORELEASE([NSMutableSet new]);
|
||
|
||
NSString *selected = RETAIN([[matrix selectedCell] title]);
|
||
unsigned existing = [[matrix cells] count];
|
||
NSFileManager *mgr = [NSFileManager defaultManager];
|
||
NSEnumerator *enumerator;
|
||
NSString *path;
|
||
NSString *name;
|
||
NSButtonCell *cell;
|
||
unsigned count = 0;
|
||
|
||
/* Ensure the first cell contains the default theme.
|
||
*/
|
||
cell = [matrix cellAtRow: count++ column: 0];
|
||
[cell setImage: [defaultTheme icon]];
|
||
[cell setTitle: [defaultTheme name]];
|
||
|
||
/* Go through all the themes in the standard locations and find their names.
|
||
*/
|
||
enumerator = [NSSearchPathForDirectoriesInDomains
|
||
(NSAllLibrariesDirectory, NSAllDomainsMask, YES) objectEnumerator];
|
||
while ((path = [enumerator nextObject]) != nil)
|
||
{
|
||
NSEnumerator *files;
|
||
NSString *file;
|
||
|
||
path = [path stringByAppendingPathComponent: @"Themes"];
|
||
files = [[mgr directoryContentsAtPath: path] objectEnumerator];
|
||
while ((file = [files nextObject]) != nil)
|
||
{
|
||
NSString *ext = [file pathExtension];
|
||
|
||
name = [file stringByDeletingPathExtension];
|
||
if ([ext isEqualToString: @"theme"] == YES
|
||
&& [name isEqualToString: @"GNUstep"] == NO)
|
||
{
|
||
[set addObject: name];
|
||
}
|
||
}
|
||
}
|
||
|
||
/* Sort theme names alphabetically, and add each theme to the matrix.
|
||
*/
|
||
array = [[set allObjects] sortedArrayUsingSelector:
|
||
@selector(caseInsensitiveCompare:)];
|
||
enumerator = [array objectEnumerator];
|
||
while ((name = [enumerator nextObject]) != nil)
|
||
{
|
||
GSTheme *loaded;
|
||
|
||
loaded = [GSTheme loadThemeNamed: name];
|
||
if (loaded != nil)
|
||
{
|
||
if (count >= existing)
|
||
{
|
||
[matrix addRow];
|
||
existing++;
|
||
}
|
||
cell = [matrix cellAtRow: count column: 0];
|
||
[cell setImage: [loaded icon]];
|
||
[cell setTitle: [loaded name]];
|
||
count++;
|
||
}
|
||
}
|
||
|
||
/* Empty any unused cells.
|
||
*/
|
||
while (count < existing)
|
||
{
|
||
cell = [matrix cellAtRow: count column: 0];
|
||
[cell setImage: nil];
|
||
[cell setTitle: @""];
|
||
count++;
|
||
}
|
||
|
||
/* Restore the selected cell.
|
||
*/
|
||
array = [matrix cells];
|
||
count = [array count];
|
||
while (count-- > 0)
|
||
{
|
||
cell = [matrix cellAtRow: count column: 0];
|
||
if ([[cell title] isEqual: selected])
|
||
{
|
||
[matrix selectCellAtRow: count column: 0];
|
||
break;
|
||
}
|
||
}
|
||
RELEASE(selected);
|
||
[matrix sizeToCells];
|
||
[matrix setNeedsDisplay: YES];
|
||
}
|
||
|
||
@end
|
||
|
||
|
||
|
||
static NSTextField *
|
||
new_label (NSString *value)
|
||
{
|
||
NSTextField *t;
|
||
|
||
t = AUTORELEASE([NSTextField new]);
|
||
[t setStringValue: value];
|
||
[t setDrawsBackground: NO];
|
||
[t setEditable: NO];
|
||
[t setSelectable: NO];
|
||
[t setBezeled: NO];
|
||
[t setBordered: NO];
|
||
[t setAlignment: NSLeftTextAlignment];
|
||
return t;
|
||
}
|
||
|
||
/* Implemented in GSInfoPanel.m
|
||
* An object that displays a list of left-aligned strings (used for the authors)
|
||
*/
|
||
@interface _GSLabelListView: NSView
|
||
{
|
||
}
|
||
/* After initialization, its size is the size it needs, just move it
|
||
where we want it to show */
|
||
- (id) initWithStringArray: (NSArray *)array
|
||
font: (NSFont *)font;
|
||
@end
|
||
|
||
|
||
@implementation GSThemeInspector
|
||
|
||
static GSThemeInspector *sharedInspector = nil;
|
||
|
||
+ (GSThemeInspector*) sharedThemeInspector
|
||
{
|
||
if (sharedInspector == nil)
|
||
{
|
||
sharedInspector = [self new];
|
||
}
|
||
return sharedInspector;
|
||
}
|
||
|
||
- (id) init
|
||
{
|
||
NSRect frame;
|
||
NSView *content;
|
||
|
||
frame.size = NSMakeSize(272,388);
|
||
frame.origin = NSZeroPoint;
|
||
self = [super initWithContentRect: frame
|
||
styleMask: (NSTitledWindowMask | NSClosableWindowMask
|
||
| NSMiniaturizableWindowMask | NSResizableWindowMask)
|
||
backing: NSBackingStoreBuffered
|
||
defer: NO];
|
||
|
||
[self setReleasedWhenClosed: NO];
|
||
content = [self contentView];
|
||
return self;
|
||
}
|
||
|
||
- (void) update: (id)sender
|
||
{
|
||
GSTheme *theme = [GSTheme theme];
|
||
NSString *details;
|
||
NSArray *authors;
|
||
NSView *content = [self contentView];
|
||
NSRect cFrame = [content frame];
|
||
NSView *view;
|
||
NSImageView *iv;
|
||
NSTextField *tf;
|
||
NSRect nameFrame;
|
||
NSRect frame;
|
||
|
||
while ((view = [[content subviews] lastObject]) != nil)
|
||
{
|
||
[view removeFromSuperview];
|
||
}
|
||
frame = NSMakeRect(cFrame.size.width - 58, cFrame.size.height - 58, 48, 48);
|
||
iv = [[NSImageView alloc] initWithFrame: frame];
|
||
[iv setImage: [[GSTheme theme] icon]];
|
||
[content addSubview: iv];
|
||
|
||
tf = new_label([theme name]);
|
||
[tf setFont: [NSFont boldSystemFontOfSize: 32]];
|
||
[tf sizeToFit];
|
||
nameFrame = [tf frame];
|
||
nameFrame.origin.x
|
||
= (cFrame.size.width - frame.size.width - nameFrame.size.width) / 2;
|
||
nameFrame.origin.y = cFrame.size.height - nameFrame.size.height - 25;
|
||
[tf setFrame: nameFrame];
|
||
[content addSubview: tf];
|
||
|
||
authors = [[theme infoDictionary] objectForKey: @"GSThemeAuthors"];
|
||
if ([authors count] > 0)
|
||
{
|
||
view = [[_GSLabelListView alloc] initWithStringArray: authors
|
||
font: [NSFont systemFontOfSize: 14]];
|
||
frame = [view frame];
|
||
frame.origin.x = (cFrame.size.width - frame.size.width) / 2;
|
||
frame.origin.y = nameFrame.origin.y - frame.size.height - 25;
|
||
[view setFrame: frame];
|
||
[content addSubview: view];
|
||
}
|
||
|
||
details = [[theme infoDictionary] objectForKey: @"GSThemeDetails"];
|
||
if ([details length] > 0)
|
||
{
|
||
NSScrollView *s;
|
||
NSTextView *v;
|
||
NSRect r;
|
||
|
||
r = NSMakeRect(10, 10, cFrame.size.width - 20, frame.origin.y - 20);
|
||
s = [[NSScrollView alloc] initWithFrame: r];
|
||
[s setHasHorizontalScroller: NO];
|
||
[s setHasVerticalScroller: YES];
|
||
[s setBorderType: NSBezelBorder];
|
||
[s setAutoresizingMask: (NSViewWidthSizable | NSViewHeightSizable)];
|
||
[content addSubview: s];
|
||
RELEASE(s);
|
||
|
||
r = [[s documentView] frame];
|
||
v = [[NSTextView alloc] initWithFrame: r];
|
||
[v setBackgroundColor: [self backgroundColor]];
|
||
[v setHorizontallyResizable: YES];
|
||
[v setVerticallyResizable: YES];
|
||
[v setEditable: NO];
|
||
[v setRichText: YES];
|
||
[v setMinSize: NSMakeSize (0, 0)];
|
||
[v setMaxSize: NSMakeSize (1E7, 1E7)];
|
||
[v setAutoresizingMask: NSViewHeightSizable | NSViewWidthSizable];
|
||
[[v textContainer] setContainerSize:
|
||
NSMakeSize (r.size.width, 1e7)];
|
||
[[v textContainer] setWidthTracksTextView: YES];
|
||
[v setString: details];
|
||
[s setDocumentView: v];
|
||
RELEASE(v);
|
||
}
|
||
|
||
[content setNeedsDisplay: YES];
|
||
}
|
||
|
||
@end
|
||
|