/**
NSColorList
Manage named lists of NSColors.
Copyright (C) 1996, 2000 Free Software Foundation, Inc.
Author: Scott Christley
Date: 1996
Author: Nicola Pero
Date: January 2000
This file is part of the GNUstep GUI Library.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; see the file COPYING.LIB.
If not, see or write to the
Free Software Foundation, 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#import "config.h"
#import
#import
#import
#import
#import
#import
#import
#import
#import
#import
#import
#import "AppKit/NSColorList.h"
#import "AppKit/NSColor.h"
#import "AppKit/AppKitExceptions.h"
// The list of available color lists is cached and re-loaded only
// after a time.
static NSMutableArray *_availableColorLists = nil;
static NSLock *_colorListLock = nil;
static NSColorList *defaultSystemColorList = nil;
static NSColorList *themeColorList = nil;
@interface NSColorList (GNUstepPrivate)
/* Loads the available color lists from standard directories.
* If called with a nil argument, this will check to see if the
* lists have already been loaded, and only load if they haven't been.
*/
+ (void) _loadAvailableColorLists: (NSNotification*)aNotification;
/* Set the default system color list ... to be used if no system color
* list has been loaded from file. This should always be the last of
* the array of available color lists even though it has noit been
* written to file.
*/
+ (void) _setDefaultSystemColorList: (NSColorList*)aList;
/* Set the theme system color list ... if this is not nil, it is placed
* at the start of the array of available lists and is used as the system
* color list.
*/
+ (void) _setThemeSystemColorList: (NSColorList*)aList;
@end
@implementation NSColorList
//
// Class methods
//
+ (void) initialize
{
if (self == [NSColorList class])
{
[self setVersion: 2];
_colorListLock = (NSLock *)[NSRecursiveLock new];
}
}
/*
* Getting All Color Lists
*/
+ (NSArray*) availableColorLists
{
NSArray *a;
// Serialize access to color list
[_colorListLock lock];
[NSColor whiteColor]; // NB This ensures that the System color list is defined
[NSColorList _loadAvailableColorLists: nil];
a = [NSArray arrayWithArray: _availableColorLists];
[_colorListLock unlock];
return a;
}
/*
* Getting a Color List by Name
*/
+ (NSColorList *) colorListNamed: (NSString *)name
{
NSColorList *r;
NSEnumerator *e;
// Serialize access to color list
[_colorListLock lock];
[NSColorList _loadAvailableColorLists: nil];
e = [_availableColorLists objectEnumerator];
while ((r = (NSColorList *)[e nextObject]) != nil)
{
if ([[r name] isEqualToString: name])
{
RETAIN(r);
break;
}
}
[_colorListLock unlock];
return AUTORELEASE(r);
}
/*
* Instance methods
*/
/*
* Private method for reading text color list files, with following format:
* first line = <#/colors>
* each subsequent line describes a color as
* the first int describes the method (ARGB, etc.), the floats
* provide its arguments (e.g., r, g, b, alpha), and string is name.
*/
- (BOOL) _readTextColorFile: (NSString *) filepath
{
int nColors;
int method;
float r;
float g;
float b;
float alpha;
NSString *cname;
int i;
BOOL st;
NSColor *color;
NSCharacterSet *newlineSet =
[NSCharacterSet characterSetWithCharactersInString: @"\n"];
NSScanner *scanner =
[NSScanner scannerWithString:
[NSString stringWithContentsOfFile: _fullFileName]];
if ([scanner scanInt: &nColors] == NO)
{
NSLog(@"Unable to read color file at \"%@\" -- unknown format.",
_fullFileName);
return NO;
}
for (i = 0; i < nColors; i++)
{
if ([scanner scanInt: &method] == NO)
{
NSLog(@"Unable to read color file at \"%@\" -- unknown format.",
_fullFileName);
break;
}
//FIXME- replace this by switch on method to different
// NSColor initializers
if (method != 0)
{
NSLog(@"Unable to read color file at \"%@\" -- only RGBA form "
@"supported.", _fullFileName);
break;
}
st = [scanner scanFloat: &r];
st = st && [scanner scanFloat: &g];
st = st && [scanner scanFloat: &b];
st = st && [scanner scanFloat: &alpha];
st = st && [scanner scanUpToCharactersFromSet: newlineSet
intoString: &cname];
if (st == NO)
{
NSLog(@"Unable to read color file at \"%@\" -- unknown format.",
_fullFileName);
break;
}
color = [NSColor colorWithCalibratedRed: r green: g blue: b alpha: alpha];
[self insertColor: color key: cname atIndex: i];
}
return i == nColors;
}
- (id) initWithName: (NSString *)name
{
return [self initWithName: name
fromFile: nil];
}
- (id) initWithName: (NSString *)name
fromFile: (NSString *)path
{
NSColorList *cl;
BOOL could_load = NO;
ASSIGN (_name, name);
if (path != nil)
{
BOOL isDir = NO;
// previously impl wrongly expected directory containing color file
// rather than color file; we support this for apps that rely on it
if (([[NSFileManager defaultManager] fileExistsAtPath: path
isDirectory: &isDir] == NO)
|| (isDir == YES))
{
NSLog(@"NSColorList -initWithName:fromFile: warning: excluding "
@"filename from path (%@) is deprecated.", path);
ASSIGN (_fullFileName, [[path stringByAppendingPathComponent: name]
stringByAppendingPathExtension: @"clr"]);
}
else
{
ASSIGN (_fullFileName, path);
}
// Unarchive the color list
// TODO [Optm]: Rewrite to initialize directly without unarchiving
// in another object
NS_DURING
{
cl =
(NSColorList*)[NSUnarchiver unarchiveObjectWithFile: _fullFileName];
}
NS_HANDLER
{
cl = nil;
}
NS_ENDHANDLER ;
if (cl && [cl isKindOfClass: [NSColorList class]])
{
could_load = YES;
_is_editable = [[NSFileManager defaultManager]
isWritableFileAtPath: _fullFileName];
ASSIGN(_colorDictionary, [NSMutableDictionary
dictionaryWithDictionary: cl->_colorDictionary]);
ASSIGN(_orderedColorKeys, [NSMutableArray
arrayWithArray: cl->_orderedColorKeys]);
}
else if ([[NSFileManager defaultManager] fileExistsAtPath: path])
{
_colorDictionary = [[NSMutableDictionary alloc] init];
_orderedColorKeys = [[NSMutableArray alloc] init];
_is_editable = YES;
if ([self _readTextColorFile: _fullFileName])
{
could_load = YES;
_is_editable = [[NSFileManager defaultManager]
isWritableFileAtPath: _fullFileName];
}
else
{
RELEASE (_colorDictionary);
RELEASE (_orderedColorKeys);
}
}
}
if (could_load == NO)
{
_fullFileName = nil;
_colorDictionary = [[NSMutableDictionary alloc] init];
_orderedColorKeys = [[NSMutableArray alloc] init];
_is_editable = YES;
}
return self;
}
- (void) dealloc
{
RELEASE (_name);
TEST_RELEASE (_fullFileName);
RELEASE (_colorDictionary);
RELEASE (_orderedColorKeys);
[super dealloc];
}
/*
* Getting a Color List by Name
*/
- (NSString *) name
{
return _name;
}
/*
* Managing Colors by Key
*/
- (NSArray *) allKeys
{
return [NSArray arrayWithArray: _orderedColorKeys];
}
- (NSColor *) colorWithKey: (NSString *)key
{
return [_colorDictionary objectForKey: key];
}
- (void) insertColor: (NSColor *)color
key: (NSString *)key
atIndex: (unsigned)location
{
NSNotification *n;
if (_is_editable == NO)
[NSException raise: NSColorListNotEditableException
format: @"Color list cannot be edited\n"];
[_colorDictionary setObject: color forKey: key];
[_orderedColorKeys removeObject: key];
[_orderedColorKeys insertObject: key atIndex: location];
n = [NSNotification notificationWithName: NSColorListDidChangeNotification
object: self
userInfo: nil];
[[NSNotificationQueue defaultQueue]
enqueueNotification: n
postingStyle: NSPostASAP
coalesceMask: NSNotificationCoalescingOnSender
forModes: nil];
}
- (void) removeColorWithKey: (NSString *)key
{
NSNotification *n;
if (_is_editable == NO)
[NSException raise: NSColorListNotEditableException
format: @"Color list cannot be edited\n"];
[_colorDictionary removeObjectForKey: key];
[_orderedColorKeys removeObject: key];
n = [NSNotification notificationWithName: NSColorListDidChangeNotification
object: self
userInfo: nil];
[[NSNotificationQueue defaultQueue]
enqueueNotification: n
postingStyle: NSPostASAP
coalesceMask: NSNotificationCoalescingOnSender
forModes: nil];
}
- (void) setColor: (NSColor *)aColor
forKey: (NSString *)key
{
NSNotification *n;
if (_is_editable == NO)
[NSException raise: NSColorListNotEditableException
format: @"Color list cannot be edited\n"];
[_colorDictionary setObject: aColor forKey: key];
if ([_orderedColorKeys containsObject: key] == NO)
[_orderedColorKeys addObject: key];
n = [NSNotification notificationWithName: NSColorListDidChangeNotification
object: self
userInfo: nil];
[[NSNotificationQueue defaultQueue]
enqueueNotification: n
postingStyle: NSPostASAP
coalesceMask: NSNotificationCoalescingOnSender
forModes: nil];
}
/*
* Editing
*/
- (BOOL) isEditable
{
return _is_editable;
}
/*
* Writing and Removing Files
*/
- (BOOL) writeToFile: (NSString *)path
{
NSFileManager *fm = [NSFileManager defaultManager];
NSString *tmpPath;
BOOL isDir;
BOOL success;
BOOL path_is_standard = YES;
/*
* We need to initialize before saving, to avoid the new file being
* counted as a different list thus making us appear twice
*/
[NSColorList _loadAvailableColorLists: nil];
if (path == nil)
{
NSArray *paths;
// FIXME the standard path for saving color lists?
paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory,
NSUserDomainMask, YES);
if ([paths count] == 0)
{
NSLog (@"Failed to find Library directory for user");
return NO; // No directory to save to.
}
path = [[paths objectAtIndex: 0]
stringByAppendingPathComponent: @"Colors"];
isDir = YES;
}
else
{
[fm fileExistsAtPath: path isDirectory: &isDir];
}
if (isDir)
{
ASSIGN (_fullFileName, [[path stringByAppendingPathComponent: _name]
stringByAppendingPathExtension: @"clr"]);
}
else // it is a file
{
if ([[path pathExtension] isEqual: @"clr"] == YES)
{
ASSIGN (_fullFileName, path);
}
else
{
ASSIGN (_fullFileName, [[path stringByDeletingPathExtension]
stringByAppendingPathExtension: @"clr"]);
}
path = [path stringByDeletingLastPathComponent];
}
// Check if the path is a standard path
if ([[path lastPathComponent] isEqualToString: @"Colors"] == NO)
{
path_is_standard = NO;
}
else
{
tmpPath = [path stringByDeletingLastPathComponent];
if (![NSSearchPathForDirectoriesInDomains(NSLibraryDirectory,
NSAllDomainsMask, YES) containsObject: tmpPath])
{
path_is_standard = NO;
}
}
/*
* If path is standard and it does not exist, try to create it.
* System standard paths should always be assumed to exist;
* this will normally then only try to create user paths.
*/
if (path_is_standard && ([fm fileExistsAtPath: path] == NO))
{
if ([fm createDirectoryAtPath: path
withIntermediateDirectories: YES
attributes: nil
error: NULL])
{
NSLog (@"Created standard directory %@", path);
}
else
{
NSLog (@"Failed attempt to create directory %@", path);
}
}
success = [NSArchiver archiveRootObject: self
toFile: _fullFileName];
if (success && path_is_standard)
{
[_colorListLock lock];
if ([_availableColorLists containsObject: self] == NO)
[_availableColorLists addObject: self];
[_colorListLock unlock];
return YES;
}
return success;
}
- (void) removeFile
{
if (_fullFileName && _is_editable)
{
// Remove the file
[[NSFileManager defaultManager] removeFileAtPath: _fullFileName
handler: nil];
// Remove the color list from the global list of colors
[_colorListLock lock];
[_availableColorLists removeObject: self];
[_colorListLock unlock];
// Reset file name
_fullFileName = nil;
}
}
- (void) encodeWithCoder: (NSCoder*)aCoder
{
if ([aCoder allowsKeyedCoding])
{
NSMutableArray *colors = [[NSMutableArray alloc] init];
NSInteger i;
for (i = 0; i < [_orderedColorKeys count]; i++)
{
NSString *name = [_orderedColorKeys objectAtIndex: i];
[colors insertObject: [_colorDictionary objectForKey: name]
atIndex: i];
}
[aCoder encodeObject: _name forKey: @"NSName"];
[aCoder encodeObject: _orderedColorKeys forKey: @"NSKeys"];
[aCoder encodeObject: colors forKey: @"NSColors"];
RELEASE(colors);
}
else
{
[aCoder encodeObject: _name];
[aCoder encodeObject: _colorDictionary];
[aCoder encodeObject: _orderedColorKeys];
}
}
- (id) initWithCoder: (NSCoder*)aDecoder
{
if ([aDecoder allowsKeyedCoding])
{
NSArray *colors;
ASSIGN(_name, [aDecoder decodeObjectForKey: @"NSName"]);
ASSIGN(_orderedColorKeys, [aDecoder decodeObjectForKey: @"NSKeys"]);
colors = [aDecoder decodeObjectForKey: @"NSColors"];
_colorDictionary = [[NSMutableDictionary alloc]
initWithObjects: colors
forKeys: _orderedColorKeys];
}
else
{
[aDecoder decodeValueOfObjCType: @encode(id) at: &_name];
[aDecoder decodeValueOfObjCType: @encode(id) at: &_colorDictionary];
[aDecoder decodeValueOfObjCType: @encode(id) at: &_orderedColorKeys];
}
return self;
}
@end
@implementation NSColorList (GNUstepPrivate)
+ (void) _loadAvailableColorLists: (NSNotification*)aNotification
{
[_colorListLock lock];
/* FIXME ... we should ensure that we get housekeeping notifications */
if (_availableColorLists != nil && aNotification == nil)
{
// Nothing to do ... already loaded
[_colorListLock unlock];
}
else
{
NSString *dir;
NSString *file;
NSEnumerator *e;
NSFileManager *fm = [NSFileManager defaultManager];
NSDirectoryEnumerator *de;
NSColorList *newList;
if (_availableColorLists == nil)
{
// Create the global array of color lists
_availableColorLists = [[NSMutableArray alloc] init];
}
else
{
[_availableColorLists removeAllObjects];
}
/*
* Keep any pre-loaded system color list.
*/
if (themeColorList != nil)
{
[_availableColorLists addObject: themeColorList];
}
/*
* Load color lists found in standard paths into the array
* FIXME: Check exactly where in the directory tree we should scan.
*/
e = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory,
NSAllDomainsMask, YES) objectEnumerator];
while ((dir = (NSString *)[e nextObject]))
{
BOOL flag;
dir = [dir stringByAppendingPathComponent: @"Colors"];
if (![fm fileExistsAtPath: dir isDirectory: &flag] || !flag)
{
// Only process existing directories
continue;
}
de = [fm enumeratorAtPath: dir];
while ((file = [de nextObject]))
{
if ([[file pathExtension] isEqualToString: @"clr"])
{
NSString *name;
name = [file stringByDeletingPathExtension];
newList = [[NSColorList alloc] initWithName: name
fromFile: [dir stringByAppendingPathComponent: file]];
[_availableColorLists addObject: newList];
RELEASE(newList);
}
}
}
if (defaultSystemColorList != nil)
{
[_availableColorLists addObject: defaultSystemColorList];
}
[_colorListLock unlock];
}
}
+ (void) _setDefaultSystemColorList: (NSColorList*)aList
{
[_colorListLock lock];
if (defaultSystemColorList != aList)
{
if (defaultSystemColorList != nil
&& [_availableColorLists lastObject] == defaultSystemColorList)
{
[_availableColorLists removeLastObject];
}
ASSIGN(defaultSystemColorList, aList);
[_availableColorLists addObject: aList];
}
[_colorListLock unlock];
}
+ (void) _setThemeSystemColorList: (NSColorList*)aList
{
[_colorListLock lock];
if (themeColorList != aList)
{
if (themeColorList != nil && [_availableColorLists count] > 0
&& [_availableColorLists objectAtIndex: 0] == themeColorList)
{
[_availableColorLists removeObjectAtIndex: 0];
}
ASSIGN(themeColorList, aList);
[_availableColorLists insertObject: aList atIndex: 0];
}
[_colorListLock unlock];
}
@end