1/** <title>GSTheme</title> 2 3 <abstract>Useful/configurable drawing functions</abstract> 4 5 Copyright (C) 2004 Free Software Foundation, Inc. 6 7 Author: Adam Fedor <fedor@gnu.org> 8 Date: Jan 2004 9 10 This file is part of the GNU Objective C User interface library. 11 12 This library is free software; you can redistribute it and/or 13 modify it under the terms of the GNU Lesser General Public 14 License as published by the Free Software Foundation; either 15 version 2 of the License, or (at your option) any later version. 16 17 This library is distributed in the hope that it will be useful, 18 but WITHOUT ANY WARRANTY; without even the implied warranty of 19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 20 Lesser General Public License for more details. 21 22 You should have received a copy of the GNU Lesser General Public 23 License along with this library; see the file COPYING.LIB. 24 If not, see <http://www.gnu.org/licenses/> or write to the 25 Free Software Foundation, 51 Franklin Street, Fifth Floor, 26 Boston, MA 02110-1301, USA. 27*/ 28 29#import <Foundation/NSBundle.h> 30#import <Foundation/NSDebug.h> 31#import <Foundation/NSDictionary.h> 32#import <Foundation/NSException.h> 33#import <Foundation/NSFileManager.h> 34#import <Foundation/NSInvocation.h> 35#import <Foundation/NSMapTable.h> 36#import <Foundation/NSMethodSignature.h> 37#import <Foundation/NSNotification.h> 38#import <Foundation/NSNull.h> 39#import <Foundation/NSPathUtilities.h> 40#import <Foundation/NSSet.h> 41#import <Foundation/NSUserDefaults.h> 42#import "GNUstepGUI/GSTheme.h" 43#import "AppKit/NSApplication.h" 44#import "AppKit/NSButtonCell.h" 45#import "AppKit/NSButton.h" 46#import "AppKit/NSColor.h" 47#import "AppKit/NSColorList.h" 48#import "AppKit/NSGraphics.h" 49#import "AppKit/NSImage.h" 50#import "AppKit/NSImageView.h" 51#import "AppKit/NSMatrix.h" 52#import "AppKit/NSMenu.h" 53#import "AppKit/NSPanel.h" 54#import "AppKit/NSScrollView.h" 55#import "AppKit/NSSegmentedControl.h" 56#import "AppKit/NSTextContainer.h" 57#import "AppKit/NSTextField.h" 58#import "AppKit/NSTextView.h" 59#import "AppKit/NSScrollView.h" 60#import "AppKit/NSView.h" 61#import "AppKit/NSWindow.h" 62#import "AppKit/NSBezierPath.h" 63#import "AppKit/PSOperators.h" 64#import "GSThemePrivate.h" 65 66NSString *GSSwitch = @"GSSwitch"; 67NSString *GSRadio = @"GSRadio"; 68 69// Scroller part names 70NSString *GSScrollerDownArrow = @"GSScrollerDownArrow"; 71NSString *GSScrollerHorizontalKnob = @"GSScrollerHorizontalKnob"; 72NSString *GSScrollerHorizontalSlot = @"GSScrollerHorizontalSlot"; 73NSString *GSScrollerLeftArrow = @"GSScrollerLeftArrow"; 74NSString *GSScrollerRightArrow = @"GSScrollerRightArrow"; 75NSString *GSScrollerUpArrow = @"GSScrollerUpArrow"; 76NSString *GSScrollerVerticalKnob = @"GSScrollerVerticalKnob"; 77NSString *GSScrollerVerticalSlot = @"GSScrollerVerticalSlot"; 78 79// Table view part names 80NSString *GSTableHeader = @"GSTableHeader"; 81NSString *GSTableCorner = @"GSTableCorner"; 82 83// Browser part names 84NSString *GSBrowserHeader = @"GSBrowserHeader"; 85 86// Menu part names 87NSString *GSMenuHorizontalBackground = @"GSMenuHorizontalBackground"; 88NSString *GSMenuVerticalBackground = @"GSMenuVerticalBackground"; 89NSString *GSMenuTitleBackground = @"GSMenuTitleBackground"; 90NSString *GSMenuHorizontalItem = @"GSMenuHorizontalItem"; 91NSString *GSMenuVerticalItem = @"GSMenuVerticalItem"; 92NSString *GSMenuSeparatorItem = @"GSMenuSeparatorItem"; 93 94// NSPopUpButton part names 95NSString *GSPopUpButton = @"GSPopUpButton"; 96 97// Progress indicator part names 98NSString *GSProgressIndicatorBezel = @"GSProgressIndicatorBezel"; 99NSString *GSProgressIndicatorBarDeterminate 100 = @"GSProgressIndicatorBarDeterminate"; 101 102// Color well part names 103NSString *GSColorWell = @"GSColorWell"; 104NSString *GSColorWellInnerBorder = @"GSColorWellInnerBorder"; 105 106// Slider part names 107NSString *GSSliderHorizontalTrack = @"GSSliderHorizontalTrack"; 108NSString *GSSliderVerticalTrack = @"GSSliderVerticalTrack"; 109 110// NSBox parts 111NSString *GSBoxBorder = @"GSBoxBorder"; 112 113/* NSTabView parts */ 114NSString *GSTabViewSelectedTabFill 115 = @"GSTabViewSelectedTabFill"; 116NSString *GSTabViewUnSelectedTabFill 117 = @"GSTabViewUnSelectedTabFill"; 118NSString *GSTabViewBackgroundTabFill 119 = @"GSTabViewBackgroundTabFill"; 120NSString *GSTabViewBottomSelectedTabFill 121 = @"GSTabViewBottomSelectedTabFill"; 122NSString *GSTabViewBottomUnSelectedTabFill 123 = @"GSTabViewBottomUnSelectedTabFill"; 124NSString *GSTabViewBottomBackgroundTabFill 125 = @"GSTabViewBottomBackgroundTabFill"; 126NSString *GSTabViewLeftSelectedTabFill 127 = @"GSTabViewLeftSelectedTabFill"; 128NSString *GSTabViewLeftUnSelectedTabFill 129 = @"GSTabViewLeftUnSelectedTabFill"; 130NSString *GSTabViewLeftBackgroundTabFill 131 = @"GSTabViewLeftBackgroundTabFill"; 132NSString *GSTabViewRightSelectedTabFill 133 = @"GSTabViewRightSelectedTabFill"; 134NSString *GSTabViewRightUnSelectedTabFill 135 = @"GSTabViewRightUnSelectedTabFill"; 136NSString *GSTabViewRightBackgroundTabFill 137 = @"GSTabViewRightBackgroundTabFill"; 138 139 140NSString *GSThemeDidActivateNotification 141 = @"GSThemeDidActivateNotification"; 142NSString *GSThemeDidDeactivateNotification 143 = @"GSThemeDidDeactivateNotification"; 144NSString *GSThemeWillActivateNotification 145 = @"GSThemeWillActivateNotification"; 146NSString *GSThemeWillDeactivateNotification 147 = @"GSThemeWillDeactivateNotification"; 148 149NSString * 150GSThemeStringFromFillStyle(GSThemeFillStyle s) 151{ 152 switch (s) 153 { 154 case GSThemeFillStyleNone: return @"None"; 155 case GSThemeFillStyleScale: return @"Scale"; 156 case GSThemeFillStyleRepeat: return @"Repeat"; 157 case GSThemeFillStyleCenter: return @"Center"; 158 case GSThemeFillStyleMatrix: return @"Matrix"; 159 case GSThemeFillStyleScaleAll: return @"ScaleAll"; 160 } 161 return nil; 162} 163 164GSThemeFillStyle 165GSThemeFillStyleFromString(NSString *s) 166{ 167 if (s == nil || [s isEqualToString: @"None"]) 168 { 169 return GSThemeFillStyleNone; 170 } 171 if ([s isEqualToString: @"Scale"]) 172 { 173 return GSThemeFillStyleScale; 174 } 175 if ([s isEqualToString: @"Repeat"]) 176 { 177 return GSThemeFillStyleRepeat; 178 } 179 if ([s isEqualToString: @"Center"]) 180 { 181 return GSThemeFillStyleCenter; 182 } 183 if ([s isEqualToString: @"Matrix"]) 184 { 185 return GSThemeFillStyleMatrix; 186 } 187 if ([s isEqualToString: @"ScaleAll"]) 188 { 189 return GSThemeFillStyleScaleAll; 190 } 191 return GSThemeFillStyleNone; 192} 193 194NSString * 195GSStringFromSegmentStyle(NSSegmentStyle segmentStyle) 196{ 197 switch (segmentStyle) 198 { 199 case NSSegmentStyleAutomatic: 200 return @"NSSegmentStyleAutomatic"; 201 case NSSegmentStyleRounded: 202 return @"NSSegmentStyleRounded"; 203 case NSSegmentStyleTexturedRounded: 204 return @"NSSegmentStyleTexturedRounded"; 205 case NSSegmentStyleRoundRect: 206 return @"NSSegmentStyleRoundRect"; 207 case NSSegmentStyleTexturedSquare: 208 return @"NSSegmentStyleTexturedSquare"; 209 case NSSegmentStyleCapsule: 210 return @"NSSegmentStyleCapsule"; 211 case NSSegmentStyleSmallSquare: 212 return @"NSSegmentStyleSmallSquare"; 213 default: 214 return nil; 215 } 216} 217 218NSString * 219GSStringFromBezelStyle(NSBezelStyle bezelStyle) 220{ 221 switch (bezelStyle) 222 { 223 case NSRoundedBezelStyle: 224 return @"NSRoundedBezelStyle"; 225 case NSRegularSquareBezelStyle: 226 return @"NSRegularSquareBezelStyle"; 227 case NSThickSquareBezelStyle: 228 return @"NSThickSquareBezelStyle"; 229 case NSThickerSquareBezelStyle: 230 return @"NSThickerSquareBezelStyle"; 231 case NSDisclosureBezelStyle: 232 return @"NSDisclosureBezelStyle"; 233 case NSShadowlessSquareBezelStyle: 234 return @"NSShadowlessSquareBezelStyle"; 235 case NSCircularBezelStyle: 236 return @"NSCircularBezelStyle"; 237 case NSTexturedSquareBezelStyle: 238 return @"NSTexturedSquareBezelStyle"; 239 case NSHelpButtonBezelStyle: 240 return @"NSHelpButtonBezelStyle"; 241 case NSSmallSquareBezelStyle: 242 return @"NSSmallSquareBezelStyle"; 243 case NSTexturedRoundedBezelStyle: 244 return @"NSTexturedRoundedBezelStyle"; 245 case NSRoundRectBezelStyle: 246 return @"NSRoundRectBezelStyle"; 247 case NSRecessedBezelStyle: 248 return @"NSRecessedBezelStyle"; 249 case NSRoundedDisclosureBezelStyle: 250 return @"NSRoundedDisclosureBezelStyle"; 251 case NSNeXTBezelStyle: 252 return @"NSNeXTBezelStyle"; 253 case NSPushButtonBezelStyle: 254 return @"NSPushButtonBezelStyle"; 255 case NSSmallIconButtonBezelStyle: 256 return @"NSSmallIconButtonBezelStyle"; 257 case NSMediumIconButtonBezelStyle: 258 return @"NSMediumIconButtonBezelStyle"; 259 case NSLargeIconButtonBezelStyle: 260 return @"NSLargeIconButtonBezelStyle"; 261 default: 262 return nil; 263 } 264} 265 266NSString * 267GSStringFromBorderType(NSBorderType borderType) 268{ 269 switch (borderType) 270 { 271 case NSNoBorder: return @"NSNoBorder"; 272 case NSLineBorder: return @"NSLineBorder"; 273 case NSBezelBorder: return @"NSBezelBorder"; 274 case NSGrooveBorder: return @"NSGrooveBorder"; 275 default: return nil; 276 } 277} 278 279NSString * 280GSStringFromTabViewType(NSTabViewType type) 281{ 282 switch (type) 283 { 284 case NSTopTabsBezelBorder: return @"NSTopTabsBezelBorder"; 285 case NSBottomTabsBezelBorder: return @"NSBottomTabsBezelBorder"; 286 case NSLeftTabsBezelBorder: return @"NSLeftTabsBezelBorder"; 287 case NSRightTabsBezelBorder: return @"NSRightTabsBezelBorder"; 288 case NSNoTabsBezelBorder: return @"NSNoTabsBezelBorder"; 289 case NSNoTabsLineBorder: return @"NSNoTabsLineBorder"; 290 case NSNoTabsNoBorder: return @"NSNoTabsNoBorder"; 291 default: return nil; 292 } 293} 294 295NSString * 296GSStringFromImageFrameStyle(NSImageFrameStyle type) 297{ 298 switch (type) 299 { 300 case NSImageFrameNone: return @"NSImageFrameNone"; 301 case NSImageFramePhoto: return @"NSImageFramePhoto"; 302 case NSImageFrameGrayBezel: return @"NSImageFrameGrayBezel"; 303 case NSImageFrameGroove: return @"NSImageFrameGroove"; 304 case NSImageFrameButton: return @"NSImageFrameButton"; 305 default: return nil; 306 } 307} 308 309@interface NSImage (Private) 310+ (void) _setImagePath: (NSString*)path name: (NSString*)name; 311+ (void) _reloadCachedImages; 312@end 313 314@interface GSTheme (Private) 315- (void) _revokeOwnerships; 316@end 317 318/* This private internal class is used to store information about a method 319 * in some other class which is overridden while the current theme is 320 * active. 321 */ 322@interface GSThemeMethod : NSObject 323{ 324@public 325 Class cls; 326 SEL sel; 327 IMP imp; // The new method implementation 328 IMP old; // The original method implementation 329 Method mth; // The method information 330} 331@end 332 333@implementation GSThemeMethod 334@end 335 336@implementation GSTheme 337 338static GSTheme *defaultTheme = nil; 339static GSTheme *theTheme = nil; 340static NSMutableDictionary *themes = nil; 341static NSNull *null = nil; 342static NSMapTable *names = 0; 343 344typedef struct { 345 NSBundle *bundle; 346 NSColorList *colors; 347 NSColorList *extraColors[GSThemeSelectedState+1]; 348 NSMutableSet *imageNames; 349 NSMutableDictionary *tiles[GSThemeSelectedState+1]; 350 NSMutableSet *owned; 351 NSImage *icon; 352 NSString *name; 353 Class colorClass; 354 Class imageClass; 355 NSMutableArray *overrides; 356} internal; 357 358#define _internal ((internal*)_reserved) 359#define _bundle _internal->bundle 360#define _colors _internal->colors 361#define _extraColors _internal->extraColors 362#define _imageNames _internal->imageNames 363#define _tiles _internal->tiles 364#define _owned _internal->owned 365#define _icon _internal->icon 366#define _name _internal->name 367#define _colorClass _internal->colorClass 368#define _imageClass _internal->imageClass 369#define _overrides _internal->overrides 370 371+ (void) defaultsDidChange: (NSNotification*)n 372{ 373 NSUserDefaults *defs; 374 NSString *name; 375 376 defs = [NSUserDefaults standardUserDefaults]; 377 name = [defs stringForKey: @"GSTheme"]; 378 if (0 == [name length]) 379 { 380 name = @"GNUstep"; 381 } 382 else if ([[name pathExtension] isEqual: @"theme"]) 383 { 384 name = [name stringByDeletingPathExtension]; 385 } 386 if (NO == [[name lastPathComponent] isEqual: [theTheme name]]) 387 { 388 [self setTheme: [self loadThemeNamed: name]]; 389 } 390} 391 392+ (void) initialize 393{ 394 if (themes == nil) 395 { 396 themes = [NSMutableDictionary new]; 397 null = RETAIN([NSNull null]); 398 defaultTheme = [[self alloc] initWithBundle: nil]; 399 ASSIGN(theTheme, defaultTheme); 400 names = NSCreateMapTable(NSNonOwnedPointerMapKeyCallBacks, 401 NSIntMapValueCallBacks, 0); 402 /* Establish the theme specified by the user defaults (if any); 403 */ 404 [self defaultsDidChange: nil]; 405 } 406} 407 408+ (GSTheme*) loadThemeNamed: (NSString*)aName 409{ 410 NSBundle *bundle; 411 Class cls; 412 GSTheme *instance; 413 NSString *theme; 414 415 if ([[aName pathExtension] isEqual: @"theme"]) 416 { 417 aName = [aName stringByDeletingPathExtension]; 418 } 419 if ([aName length] == 0 || [[aName lastPathComponent] isEqual: @"GNUstep"]) 420 { 421 return defaultTheme; 422 } 423 424 if ([aName isAbsolutePath] == YES) 425 { 426 theme = aName; 427 } 428 else 429 { 430 aName = [aName lastPathComponent]; 431 } 432 433 /* Ensure that the theme name has the 'theme' extension. 434 */ 435 if ([[aName pathExtension] isEqualToString: @"theme"] == YES) 436 { 437 theme = aName; 438 } 439 else 440 { 441 theme = [aName stringByAppendingPathExtension: @"theme"]; 442 } 443 444 bundle = [themes objectForKey: theme]; 445 if (bundle == nil) 446 { 447 NSString *path = nil; 448 NSFileManager *mgr = [NSFileManager defaultManager]; 449 BOOL isDir; 450 451 /* A theme may be either an absolute path or a filename to be located 452 * in the Themes subdirectory of one of the standard Library directories. 453 */ 454 if ([theme isAbsolutePath] == YES) 455 { 456 if ([mgr fileExistsAtPath: theme isDirectory: &isDir] == YES 457 && isDir == YES) 458 { 459 path = theme; 460 } 461 } 462 else 463 { 464 NSEnumerator *enumerator; 465 466 enumerator = [NSSearchPathForDirectoriesInDomains 467 (NSAllLibrariesDirectory, NSAllDomainsMask, YES) objectEnumerator]; 468 while ((path = [enumerator nextObject]) != nil) 469 { 470 path = [path stringByAppendingPathComponent: @"Themes"]; 471 path = [path stringByAppendingPathComponent: theme]; 472 if ([mgr fileExistsAtPath: path isDirectory: &isDir]) 473 { 474 break; 475 } 476 } 477 } 478 479 if (path == nil) 480 { 481 NSLog (@"No theme named '%@' found", aName); 482 return nil; 483 } 484 else 485 { 486 bundle = [NSBundle bundleWithPath: path]; 487 [themes setObject: bundle forKey: theme]; 488 [bundle load]; // Ensure code is loaded. 489 } 490 } 491 492 cls = [bundle principalClass]; 493 if (cls == 0) 494 { 495 cls = self; 496 } 497 instance = [[cls alloc] initWithBundle: bundle]; 498 return AUTORELEASE(instance); 499} 500 501+ (void) orderFrontSharedThemePanel: (id)sender 502{ 503 GSThemePanel *panel; 504 505 panel = [GSThemePanel sharedThemePanel]; 506 [panel update: self]; 507 [panel center]; 508 [panel orderFront: self]; 509} 510 511+ (void) setTheme: (GSTheme*)theme 512{ 513 if (theme == nil) 514 { 515 theme = defaultTheme; 516 } 517 if (theme != theTheme) 518 { 519 /* 520 * Remove any previous observers... 521 */ 522 [[NSNotificationCenter defaultCenter] 523 removeObserver: self]; 524 525 [theTheme deactivate]; 526 ASSIGN (theTheme, theme); 527 [theTheme activate]; 528 529 /* 530 * Listen to notifications... 531 */ 532 [[NSNotificationCenter defaultCenter] 533 addObserver: self 534 selector: @selector(defaultsDidChange:) 535 name: NSUserDefaultsDidChangeNotification 536 object: nil]; 537 } 538} 539 540+ (GSTheme*) theme 541{ 542 return theTheme; 543} 544 545- (void) activate 546{ 547 NSUserDefaults *defs; 548 NSMutableArray *searchList; 549 NSEnumerator *enumerator; 550 NSDictionary *infoDict; 551 NSWindow *window; 552 GSThemeControlState state; 553 554 NSDebugMLLog(@"GSTheme", @"%@ %p", [self name], self); 555 /* Get rid of any cached colors list so that we regenerate it when needed 556 */ 557 [_colors release]; 558 _colors = nil; 559 for (state = 0; state <= GSThemeSelectedState; state++) 560 { 561 [_extraColors[state] release]; 562 _extraColors[state] = nil; 563 } 564 565 /* 566 * Reload NSImage's cache of image by name 567 */ 568 [NSImage _reloadCachedImages]; 569 570 /* 571 * Use the GSThemeDomain key in the info dictionary of the theme to 572 * set a defaults domain which will establish user defaults values 573 * but will not override any defaults set explicitly by the user. 574 * NB. For subclasses, the theme info dictionary may not be the same 575 * as that of the bundle, so we don't use the bundle method directly. 576 */ 577 infoDict = [self infoDictionary]; 578 defs = [NSUserDefaults standardUserDefaults]; 579 searchList = [[defs searchList] mutableCopy]; 580 if ([[infoDict objectForKey: @"GSThemeDomain"] isKindOfClass: 581 [NSDictionary class]] == YES) 582 { 583 [defs removeVolatileDomainForName: @"GSThemeDomain"]; 584 [defs setVolatileDomain: [infoDict objectForKey: @"GSThemeDomain"] 585 forName: @"GSThemeDomain"]; 586 if ([searchList containsObject: @"GSThemeDomain"] == NO) 587 { 588 NSUInteger index; 589 590 /* 591 * Higher priority than GSConfigDomain and NSRegistrationDomain, 592 * but lower than NSGlobalDomain, NSArgumentDomain, and others 593 * set by the user to be application specific. 594 */ 595 index = [searchList indexOfObject: GSConfigDomain]; 596 if (index == NSNotFound) 597 { 598 index = [searchList indexOfObject: NSRegistrationDomain]; 599 if (index == NSNotFound) 600 { 601 index = [searchList count]; 602 } 603 } 604 [searchList insertObject: @"GSThemeDomain" atIndex: index]; 605 [defs setSearchList: searchList]; 606 } 607 } 608 else 609 { 610 [searchList removeObject: @"GSThemeDomain"]; 611 [defs removeVolatileDomainForName: @"GSThemeDomain"]; 612 } 613 RELEASE(searchList); 614 615 /* Install any overridden methods. 616 */ 617 if (_overrides != nil) 618 { 619 NSEnumerator *e = [_overrides objectEnumerator]; 620 GSThemeMethod *m; 621 622 while ((m = [e nextObject]) != nil) 623 { 624 method_setImplementation(m->mth, m->imp); 625 } 626 } 627 628 /* 629 * Tell subclass that basic activation is done and it can do its own. 630 */ 631 [[NSNotificationCenter defaultCenter] 632 postNotificationName: GSThemeWillActivateNotification 633 object: self 634 userInfo: nil]; 635 636 /* 637 * Tell all other classes that new theme information is present. 638 */ 639 [[NSNotificationCenter defaultCenter] 640 postNotificationName: GSThemeDidActivateNotification 641 object: self 642 userInfo: nil]; 643 644 /* 645 * Reset main menu to change between styles if necessary 646 */ 647 [[NSApp mainMenu] setMain: YES]; 648 649 /* 650 * Mark all windows as needing redisplaying to show the new theme. 651 */ 652 enumerator = [[NSApp windows] objectEnumerator]; 653 while ((window = [enumerator nextObject]) != nil) 654 { 655 [[[window contentView] superview] setNeedsDisplay: YES]; 656 } 657} 658 659- (NSArray*) authors 660{ 661 return [[self infoDictionary] objectForKey: @"GSThemeAuthors"]; 662} 663 664- (NSBundle*) bundle 665{ 666 return _bundle; 667} 668 669- (Class) colorClass 670{ 671 return [NSColorList class]; 672} 673 674- (void) colorFlush: (NSString*)aName 675 state: (GSThemeControlState)elementState 676{ 677 int pos; 678 int end; 679 680 if (elementState > GSThemeSelectedState) 681 { 682 pos = 0; 683 end = GSThemeSelectedState; 684 } 685 else 686 { 687 pos = elementState; 688 end = elementState; 689 } 690 while (pos <= end) 691 { 692 if (_extraColors[pos] != nil) 693 { 694 [_extraColors[pos] release]; 695 _extraColors[pos] = nil; 696 } 697 pos++; 698 } 699} 700 701- (NSColor*) colorNamed: (NSString*)aName 702 state: (GSThemeControlState)elementState 703{ 704 NSColor *c = nil; 705 706 NSAssert(elementState <= GSThemeSelectedState, NSInvalidArgumentException); 707 NSAssert(elementState >= 0, NSInvalidArgumentException); 708 709 if (aName != nil) 710 { 711 if (_extraColors[elementState] == nil) 712 { 713 NSString *colorsPath; 714 NSString *listName; 715 NSString *resourceName; 716 717 /* Attempt to load color list ... if the list is not found 718 * or the load fails, set a null marker. 719 */ 720 switch (elementState) 721 { 722 default: 723 case GSThemeNormalState: 724 listName = @"ThemeExtra"; 725 break; 726 case GSThemeHighlightedState: 727 listName = @"ThemeExtraHighlighted"; 728 break; 729 case GSThemeSelectedState: 730 listName = @"ThemeExtraSelected"; 731 break; 732 } 733 resourceName = [listName stringByAppendingString: @"Colors"]; 734 colorsPath = [_bundle pathForResource: resourceName 735 ofType: @"clr"]; 736 if (colorsPath != nil) 737 { 738 _extraColors[elementState] 739 = [[_colorClass alloc] initWithName: listName 740 fromFile: colorsPath]; 741 /* If the list is actually empty, we get rid of it to avoid 742 * unnecessary lookups. 743 */ 744 if ([[_extraColors[elementState] allKeys] count] == 0) 745 { 746 [_extraColors[elementState] release]; 747 _extraColors[elementState] = nil; 748 } 749 } 750 if (_extraColors[elementState] == nil) 751 { 752 _extraColors[elementState] = (id)[null retain]; 753 } 754 } 755 if (_extraColors[elementState] != (id)null) 756 { 757 c = [_extraColors[elementState] colorWithKey: aName]; 758 } 759 } 760 return c; 761} 762 763- (NSColorList*) colors 764{ 765 if (_colors == nil) 766 { 767 NSString *colorsPath; 768 769 colorsPath = [_bundle pathForResource: @"ThemeColors" ofType: @"clr"]; 770 if (colorsPath == nil) 771 { 772 _colors = (id)[null retain]; 773 } 774 else 775 { 776 _colors = [[_colorClass alloc] initWithName: @"System" 777 fromFile: colorsPath]; 778 } 779 } 780 if ((id)_colors == (id)null) 781 { 782 return nil; 783 } 784 return _colors; 785} 786 787- (void) deactivate 788{ 789 NSDebugMLLog(@"GSTheme", @"%@ %p", [self name], self); 790 791 /* Tell everything that we will become inactive. 792 */ 793 [[NSNotificationCenter defaultCenter] 794 postNotificationName: GSThemeWillDeactivateNotification 795 object: self 796 userInfo: nil]; 797 798 /* Remove any overridden methods. 799 */ 800 if (_overrides != nil) 801 { 802 NSEnumerator *e = [_overrides objectEnumerator]; 803 GSThemeMethod *m; 804 805 while ((m = [e nextObject]) != nil) 806 { 807 method_setImplementation(m->mth, m->old); 808 } 809 } 810 811 [self _revokeOwnerships]; 812 813 /* Tell everything that we have become inactive. 814 */ 815 [[NSNotificationCenter defaultCenter] 816 postNotificationName: GSThemeDidDeactivateNotification 817 object: self 818 userInfo: nil]; 819} 820 821- (void) dealloc 822{ 823 if (_reserved != 0) 824 { 825 GSThemeControlState state; 826 827 for (state = 0; state <= GSThemeSelectedState; state++) 828 { 829 RELEASE(_extraColors[state]); 830 RELEASE(_tiles[state]); 831 } 832 RELEASE(_bundle); 833 RELEASE(_colors); 834 RELEASE(_imageNames); 835 RELEASE(_icon); 836 [self _revokeOwnerships]; 837 RELEASE(_overrides); 838 RELEASE(_owned); 839 NSZoneFree ([self zone], _reserved); 840 } 841 [super dealloc]; 842} 843 844- (NSImage*) icon 845{ 846 if (_icon == nil) 847 { 848 NSString *path; 849 850 path = [[self infoDictionary] objectForKey: @"GSThemeIcon"]; 851 if (path != nil) 852 { 853 NSString *ext = [path pathExtension]; 854 855 path = [path stringByDeletingPathExtension]; 856 path = [_bundle pathForResource: path ofType: ext]; 857 if (path != nil) 858 { 859 _icon = [[_imageClass alloc] initWithContentsOfFile: path]; 860 } 861 } 862 if (_icon == nil) 863 { 864 _icon = RETAIN([_imageClass imageNamed: @"GNUstep"]); 865 } 866 else 867 { 868 NSSize s = [_icon size]; 869 float scale = 1.0; 870 871 if (s.height > 48.0) 872 scale = 48.0 / s.height; 873 if (48.0 / s.width < scale) 874 scale = 48.0 / s.width; 875 if (scale != 1.0) 876 { 877 [_icon setScalesWhenResized: YES]; 878 s.height *= scale; 879 s.width *= scale; 880 [_icon setSize: s]; 881 } 882 } 883 } 884 return _icon; 885} 886 887- (Class) imageClass 888{ 889 return [NSImage class]; 890} 891 892- (id) initWithBundle: (NSBundle*)bundle 893{ 894 Class c = [self class]; 895 unsigned int count; 896 Method *methods; 897 GSThemeMethod *mth; 898 GSThemeControlState state; 899 900 _reserved = NSZoneCalloc ([self zone], 1, sizeof(internal)); 901 902 ASSIGN(_bundle, bundle); 903 _imageNames = [NSMutableSet new]; 904 for (state = 0; state <= GSThemeSelectedState; state++) 905 { 906 _tiles[state] = [NSMutableDictionary new]; 907 } 908 _owned = [NSMutableSet new]; 909 910 ASSIGN(_name, 911 [[[_bundle bundlePath] lastPathComponent] stringByDeletingPathExtension]); 912 913 _colorClass = [self colorClass]; 914 _imageClass = [self imageClass]; 915 916 /* Now we look through our methods to find those which are actually 917 * replacements to override methods in other classes. 918 * That's determined by method name ... any method of the form 919 * '_override' <classname> 'Method_' <originalmethodname> 920 * is used to replace the original method in the class. 921 * We maintain dictionaries (keyed by class) for instance and class 922 * methods, so we can look up the original methods at runtime if the 923 * replacement methods want to call them. 924 */ 925 methods = class_copyMethodList(c, &count); 926 if (methods != NULL) 927 { 928 int counter = 0; 929 930 while (methods[counter] != 0) 931 { 932 Method method = methods[counter++]; 933 const char *name = sel_getName(method_getName(method)); 934 const char *ptr; 935 936 if (strncmp(name, "_override", 9) == 0 937 && (ptr = strstr(name, "Method_")) > 0) 938 { 939 char buf[strlen(name)]; 940 const char *types; 941 942 mth = [[GSThemeMethod new] autorelease]; 943 types = method_getTypeEncoding(method); 944 mth->imp = method_getImplementation(method); 945 memcpy(buf, name + 9, (ptr - name) + 9); 946 buf[(ptr - name) + 9] = '\0'; 947 mth->cls = objc_lookUpClass(buf); 948 if (mth->cls == 0) 949 { 950 NSLog(@"Unable to find class '%s' for '%s'", buf, name); 951 continue; 952 } 953 memcpy(buf, ptr + 7, strlen(ptr + 7)); 954 buf[strlen(ptr + 7)] = '\0'; 955 mth->sel = sel_getUid(buf); 956 if (mth->sel == 0) 957 { 958 NSLog(@"Unable to find selector '-%s' for '%s'", buf, name); 959 continue; 960 } 961 if (NO == [mth->cls instancesRespondToSelector: mth->sel]) 962 { 963 NSLog(@"Instances do not respond for '%s'", name); 964 continue; 965 } 966 mth->old = [mth->cls instanceMethodForSelector: mth->sel]; 967 class_addMethod(mth->cls, mth->sel, mth->imp, types); 968 mth->mth = class_getInstanceMethod(mth->cls, mth->sel); 969 970 if (_overrides == nil) 971 { 972 _overrides = [NSMutableArray new]; 973 } 974 [_overrides addObject: mth]; 975 } 976 } 977 free(methods); 978 } 979 980 methods = class_copyMethodList(object_getClass(c), &count); 981 if (methods != NULL) 982 { 983 int counter = 0; 984 985 while (methods[counter] != 0) 986 { 987 Method method = methods[counter++]; 988 const char *name = sel_getName(method_getName(method)); 989 const char *ptr; 990 991 if (strncmp(name, "_override", 9) == 0 992 && (ptr = strstr(name, "Method_")) > 0) 993 { 994 char buf[strlen(name)]; 995 const char *types; 996 Class cls; 997 998 mth = [[GSThemeMethod new] autorelease]; 999 types = method_getTypeEncoding(method); 1000 mth->imp = method_getImplementation(method); 1001 memcpy(buf, name + 9, (ptr - name) + 9); 1002 buf[(ptr - name) + 9] = '\0'; 1003 cls = objc_lookUpClass(buf); 1004 if (cls == 0) 1005 { 1006 NSLog(@"Unable to find class '%s' for '%s'", buf, name); 1007 continue; 1008 } 1009 mth->cls = object_getClass(cls); 1010 memcpy(buf, ptr + 7, strlen(ptr + 7)); 1011 buf[strlen(ptr + 7)] = '\0'; 1012 mth->sel = sel_getUid(buf); 1013 if (mth->sel == 0) 1014 { 1015 NSLog(@"Unable to find selector '-%s' for '%s'", buf, name); 1016 continue; 1017 } 1018 if (NO == [cls respondsToSelector: mth->sel]) 1019 { 1020 NSLog(@"Class does not respond for '%s'", name); 1021 continue; 1022 } 1023 mth->old = [cls methodForSelector: mth->sel]; 1024 class_addMethod(mth->cls, mth->sel, mth->imp, types); 1025 mth->mth = class_getClassMethod(cls, mth->sel); 1026 1027 if (_overrides == nil) 1028 { 1029 _overrides = [NSMutableArray new]; 1030 } 1031 [_overrides addObject: mth]; 1032 } 1033 } 1034 free(methods); 1035 } 1036 1037 return self; 1038} 1039 1040- (NSDictionary*) infoDictionary 1041{ 1042 return [_bundle infoDictionary]; 1043} 1044 1045- (NSString*) name 1046{ 1047 if (self == defaultTheme) 1048 { 1049 _name = @"GNUstep"; 1050 } 1051 return _name; 1052} 1053 1054- (NSString*) nameForElement: (id)anObject 1055{ 1056 NSString *name = (NSString*)NSMapGet(names, (void*)anObject); 1057 1058 return name; 1059} 1060 1061- (IMP) overriddenMethod: (SEL)selector for: (id)receiver 1062{ 1063 Class cls = object_getClass(receiver); 1064 NSEnumerator *e = [_overrides objectEnumerator]; 1065 GSThemeMethod *m; 1066 1067 while ((m = [e nextObject]) != nil) 1068 { 1069 if (m->cls == cls && sel_isEqual(selector, m->sel)) 1070 { 1071 return m->old; 1072 } 1073 } 1074 return (IMP)0; 1075} 1076 1077- (void) setName: (NSString*)aString 1078{ 1079 if (self != defaultTheme) 1080 { 1081 ASSIGNCOPY(_name, aString); 1082 } 1083} 1084 1085- (void) setName: (NSString*)aString 1086 forElement: (id)anObject 1087 temporary: (BOOL)takeOwnership 1088{ 1089 if (aString == nil) 1090 { 1091 if (anObject == nil) 1092 { 1093 /* Ignore this ... it's most likely a partially initialised 1094 * control being deallocated and removing the name for a 1095 * subsidiary item which was never allocated in the first place. 1096 */ 1097 return; 1098 } 1099 NSMapRemove(names, (void*)anObject); 1100 [_owned removeObject: anObject]; 1101 } 1102 else 1103 { 1104 if (anObject == nil) 1105 { 1106 [NSException raise: NSInvalidArgumentException 1107 format: @"[%@-%@] nil object supplied", 1108 NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; 1109 } 1110 NSMapInsert(names, (void*)anObject, (void*)aString); 1111 if (takeOwnership == YES) 1112 { 1113 [_owned addObject: anObject]; 1114 } 1115 else 1116 { 1117 [_owned removeObject: anObject]; 1118 } 1119 } 1120} 1121 1122- (NSWindow*) themeInspector 1123{ 1124 return [GSThemeInspector sharedThemeInspector]; 1125} 1126 1127- (void) tilesFlush: (NSString*)aName 1128 state: (GSThemeControlState)elementState 1129{ 1130 int pos; 1131 int end; 1132 1133 if (elementState > GSThemeSelectedState) 1134 { 1135 pos = 0; 1136 end = GSThemeSelectedState; 1137 } 1138 else 1139 { 1140 pos = elementState; 1141 end = elementState; 1142 } 1143 while (pos <= end) 1144 { 1145 NSMutableDictionary *cache; 1146 1147 cache = _tiles[pos++]; 1148 if (aName == nil) 1149 { 1150 return [cache removeAllObjects]; 1151 } 1152 else 1153 { 1154 [cache removeObjectForKey: aName]; 1155 } 1156 } 1157} 1158 1159- (GSDrawTiles*) tilesNamed: (NSString*)aName 1160 state: (GSThemeControlState)elementState 1161{ 1162 GSDrawTiles *tiles; 1163 NSMutableDictionary *cache; 1164 1165 NSAssert(elementState <= GSThemeSelectedState, NSInvalidArgumentException); 1166 NSAssert(elementState >= 0, NSInvalidArgumentException); 1167 if (aName == nil) 1168 { 1169 return nil; 1170 } 1171 cache = _tiles[elementState]; 1172 tiles = [cache objectForKey: aName]; 1173 if (tiles == nil) 1174 { 1175 NSDictionary *info; 1176 NSImage *image; 1177 NSString *fullName; 1178 1179 switch (elementState) 1180 { 1181 default: 1182 case GSThemeNormalState: 1183 fullName = aName; 1184 break; 1185 case GSThemeFirstResponderState: 1186 fullName = [aName stringByAppendingString: @"FirstResponder"]; 1187 break; 1188 case GSThemeDisabledState: 1189 fullName = [aName stringByAppendingString: @"Disabled"]; 1190 break; 1191 case GSThemeHighlightedFirstResponderState: 1192 fullName 1193 = [aName stringByAppendingString: @"HighlightedFirstResponder"]; 1194 break; 1195 case GSThemeHighlightedState: 1196 fullName = [aName stringByAppendingString: @"Highlighted"]; 1197 break; 1198 case GSThemeSelectedFirstResponderState: 1199 fullName 1200 = [aName stringByAppendingString: @"SelectedFirstResponder"]; 1201 break; 1202 case GSThemeSelectedState: 1203 fullName = [aName stringByAppendingString: @"Selected"]; 1204 break; 1205 } 1206 1207 /* The GSThemeTiles entry in the info dictionary should be a 1208 * dictionary containing information about each set of tiles. 1209 * Keys are: 1210 * FileName Name of the file in the ThemeTiles directory 1211 * HorizontalDivision Where to divide the image into columns. 1212 * VerticalDivision Where to divide the image into rows. 1213 */ 1214 info = [self infoDictionary]; 1215 info = [[info objectForKey: @"GSThemeTiles"] objectForKey: fullName]; 1216 if ([info isKindOfClass: [NSDictionary class]] == YES) 1217 { 1218 float x; 1219 float y; 1220 NSString *name; 1221 NSString *path; 1222 NSString *file; 1223 NSString *ext; 1224 GSThemeFillStyle style; 1225 1226 name = [info objectForKey: @"FillStyle"]; 1227 style = GSThemeFillStyleFromString(name); 1228 if (style < GSThemeFillStyleNone) style = GSThemeFillStyleNone; 1229 x = [[info objectForKey: @"HorizontalDivision"] floatValue]; 1230 y = [[info objectForKey: @"VerticalDivision"] floatValue]; 1231 file = [info objectForKey: @"FileName"]; 1232 ext = [file pathExtension]; 1233 file = [file stringByDeletingPathExtension]; 1234 path = [_bundle pathForResource: file 1235 ofType: ext 1236 inDirectory: @"ThemeTiles"]; 1237 if (path == nil) 1238 { 1239 NSLog(@"File %@.%@ not found in ThemeTiles", file, ext); 1240 } 1241 else 1242 { 1243 image = [[_imageClass alloc] initWithContentsOfFile: path]; 1244 if (image != nil) 1245 { 1246 if ([[info objectForKey: @"NinePatch"] boolValue] 1247 || [file hasSuffix: @".9"]) 1248 { 1249 tiles = [[GSDrawTiles alloc] 1250 initWithNinePatchImage: image]; 1251 [tiles setFillStyle: GSThemeFillStyleScaleAll]; 1252 } 1253 else 1254 { 1255 tiles = [[GSDrawTiles alloc] initWithImage: image 1256 horizontal: x 1257 vertical: y]; 1258 [tiles setFillStyle: style]; 1259 } 1260 RELEASE(image); 1261 } 1262 } 1263 } 1264 1265 if (tiles == nil) 1266 { 1267 NSString *imagePath; 1268 1269 // Try 9-patch first 1270 imagePath = [_bundle pathForResource: fullName 1271 ofType: @"9.png" 1272 inDirectory: @"ThemeTiles"]; 1273 if (imagePath != nil) 1274 { 1275 image 1276 = [[_imageClass alloc] initWithContentsOfFile: imagePath]; 1277 if (image != nil) 1278 { 1279 tiles = [[GSDrawTiles alloc] 1280 initWithNinePatchImage: image]; 1281 [tiles setFillStyle: GSThemeFillStyleScaleAll]; 1282 RELEASE(image); 1283 } 1284 } 1285 } 1286 1287 if (tiles == nil) 1288 { 1289 NSArray *imageTypes; 1290 NSString *imagePath; 1291 unsigned count; 1292 1293 imageTypes = [_imageClass imageFileTypes]; 1294 for (count = 0; count < [imageTypes count]; count++) 1295 { 1296 NSString *ext = [imageTypes objectAtIndex: count]; 1297 1298 imagePath = [_bundle pathForResource: fullName 1299 ofType: ext 1300 inDirectory: @"ThemeTiles"]; 1301 if (imagePath != nil) 1302 { 1303 image 1304 = [[_imageClass alloc] initWithContentsOfFile: imagePath]; 1305 if (image != nil) 1306 { 1307 tiles = [[GSDrawTiles alloc] initWithImage: image]; 1308 RELEASE(image); 1309 break; 1310 } 1311 } 1312 } 1313 } 1314 1315 if (tiles == nil) 1316 { 1317 [cache setObject: null forKey: aName]; 1318 } 1319 else 1320 { 1321 [cache setObject: tiles forKey: aName]; 1322 RELEASE(tiles); 1323 } 1324 } 1325 if (tiles == (id)null) 1326 { 1327 tiles = nil; 1328 } 1329 return tiles; 1330} 1331 1332- (NSString*) versionString 1333{ 1334 return [[self infoDictionary] objectForKey: @"GSThemeVersion"]; 1335} 1336 1337- (NSString *) license 1338{ 1339 return [[self infoDictionary] objectForKey: @"GSThemeLicense"]; 1340} 1341 1342@end 1343 1344@implementation GSTheme (Private) 1345/* Remove all temporarily named objects from our registry, releasing them. 1346 */ 1347- (void) _revokeOwnerships 1348{ 1349 id o; 1350 1351 while ((o = [_owned anyObject]) != nil) 1352 { 1353 [self setName: nil forElement: o temporary: YES]; 1354 } 1355} 1356@end 1357 1358@implementation GSThemeProxy 1359- (id) _resource 1360{ 1361 return _resource; 1362} 1363- (void) _setResource: (id)resource 1364{ 1365 ASSIGN(_resource, resource); 1366} 1367- (void) dealloc 1368{ 1369 DESTROY(_resource); 1370 [super dealloc]; 1371} 1372- (NSString*) description 1373{ 1374 return [_resource description]; 1375} 1376- (id) forwardingTargetForSelector:(SEL)aSelector 1377{ 1378 return _resource; 1379} 1380- (void) forwardInvocation: (NSInvocation*)anInvocation 1381{ 1382 [anInvocation invokeWithTarget: _resource]; 1383} 1384- (NSMethodSignature*) methodSignatureForSelector: (SEL)aSelector 1385{ 1386 if (_resource != nil) 1387 { 1388 return [_resource methodSignatureForSelector: aSelector]; 1389 } 1390 else 1391 { 1392 /* 1393 * Evil hack to prevent recursion - if we are asking a remote 1394 * object for a method signature, we can't ask it for the 1395 * signature of methodSignatureForSelector:, so we hack in 1396 * the signature required manually :-( 1397 */ 1398 if (sel_isEqual(aSelector, _cmd)) 1399 { 1400 static NSMethodSignature *sig = nil; 1401 1402 if (sig == nil) 1403 { 1404 sig = RETAIN([NSMethodSignature signatureWithObjCTypes: "@@::"]); 1405 } 1406 return sig; 1407 } 1408 return nil; 1409 } 1410} 1411@end 1412 1413