mirror of
https://github.com/gnustep/libs-gui.git
synced 2025-05-30 02:10:37 +00:00
Major revision of services - all menu services stuff in place now.
git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/gui/trunk@3363 72102866-910b-0410-8b05-ffd578937521
This commit is contained in:
parent
558b6da8c2
commit
141a4d603a
11 changed files with 1434 additions and 861 deletions
|
@ -64,641 +64,7 @@
|
|||
[a release]; \
|
||||
a = b;
|
||||
|
||||
|
||||
//*****************************************************************************
|
||||
//
|
||||
// ApplicationListener
|
||||
//
|
||||
//*****************************************************************************
|
||||
|
||||
extern NSDictionary *GSAllServicesDictionary();
|
||||
|
||||
/*
|
||||
* Local class for handling incoming service requests etc.
|
||||
*/
|
||||
@interface ApplicationListener : NSObject
|
||||
{
|
||||
id servicesProvider;
|
||||
NSApplication *application;
|
||||
NSMenu *servicesMenu;
|
||||
NSMutableArray *languages;
|
||||
NSMutableSet *returnInfo;
|
||||
NSMutableDictionary *combinations;
|
||||
NSMutableDictionary *title2info;
|
||||
NSArray *menuTitles;
|
||||
BOOL isRegistered;
|
||||
}
|
||||
+ (ApplicationListener*) newWithApplication: (NSApplication*)app;
|
||||
- (void) doService: (NSCell*)item;
|
||||
- (BOOL) hasRegisteredTypes: (NSDictionary*)service;
|
||||
- (NSString*) item2title: (NSCell*)item;
|
||||
- (void) rebuildServices;
|
||||
- (void) rebuildServicesMenu;
|
||||
- (void) registerAsServiceProvider;
|
||||
- (void) registerSendTypes: (NSArray *)sendTypes
|
||||
returnTypes: (NSArray *)returnTypes;
|
||||
- (NSMenu *) servicesMenu;
|
||||
- (id) servicesProvider;
|
||||
- (void) setServicesMenu: (NSMenu *)anObject;
|
||||
- (void) setServicesProvider: (id)anObject;
|
||||
- (BOOL) validateMenuItem: (NSCell*)item;
|
||||
- (void) updateServicesMenu;
|
||||
@end
|
||||
|
||||
@implementation ApplicationListener
|
||||
/*
|
||||
* Create a new listener for this application.
|
||||
* Uses NSRegisterServicesProvider() to register itsself as a service
|
||||
* provider with the applications name so we can handle incoming
|
||||
* service requests.
|
||||
*/
|
||||
+ (ApplicationListener*) newWithApplication: (NSApplication*)app
|
||||
{
|
||||
ApplicationListener *listener = [ApplicationListener alloc];
|
||||
|
||||
/*
|
||||
* Don't retain application object - that would reate a cycle.
|
||||
*/
|
||||
listener->application = app;
|
||||
listener->returnInfo = [[NSMutableSet alloc] initWithCapacity: 16];
|
||||
listener->combinations = [[NSMutableDictionary alloc] initWithCapacity: 16];
|
||||
return listener;
|
||||
}
|
||||
|
||||
- (void) doService: (NSCell*)item
|
||||
{
|
||||
NSString *title = [self item2title: item];
|
||||
NSDictionary *info = [title2info objectForKey: title];
|
||||
NSArray *sendTypes = [info objectForKey: @"NSSendTypes"];
|
||||
NSArray *returnTypes = [info objectForKey: @"NSReturnTypes"];
|
||||
unsigned i, j;
|
||||
unsigned es = [sendTypes count];
|
||||
unsigned er = [returnTypes count];
|
||||
NSWindow *resp = [[application keyWindow] firstResponder];
|
||||
id obj = nil;
|
||||
|
||||
for (i = 0; i <= es; i++)
|
||||
{
|
||||
NSString *sendType;
|
||||
|
||||
sendType = (i < es) ? [sendTypes objectAtIndex: i] : nil;
|
||||
|
||||
for (j = 0; j <= er; j++)
|
||||
{
|
||||
NSString *returnType;
|
||||
|
||||
returnType = (j < er) ? [returnTypes objectAtIndex: j] : nil;
|
||||
|
||||
obj = [resp validRequestorForSendType: sendType
|
||||
returnType: returnType];
|
||||
if (obj != nil)
|
||||
{
|
||||
NSPasteboard *pb;
|
||||
|
||||
pb = [NSPasteboard pasteboardWithUniqueName];
|
||||
if ([obj writeSelectionToPasteboard: pb
|
||||
types: sendTypes] == NO)
|
||||
{
|
||||
NSLog(@"object failed to write to pasteboard\n");
|
||||
}
|
||||
else if (NSPerformService(title, pb) == NO)
|
||||
{
|
||||
NSLog(@"Failed to perform %@\n", title);
|
||||
}
|
||||
else if ([obj readSelectionFromPasteboard: pb] == NO)
|
||||
{
|
||||
NSLog(@"object failed to read from pasteboard\n");
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL) hasRegisteredTypes: (NSDictionary*)service
|
||||
{
|
||||
NSArray *sendTypes = [service objectForKey: @"NSSendTypes"];
|
||||
NSArray *returnTypes = [service objectForKey: @"NSReturnTypes"];
|
||||
NSString *type;
|
||||
unsigned i;
|
||||
|
||||
/*
|
||||
* We know that both sendTypes and returnTypes can't be nil since
|
||||
* make_services has validated the service entry for us.
|
||||
*/
|
||||
if (sendTypes == nil || [sendTypes count] == 0)
|
||||
{
|
||||
for (i = 0; i < [returnTypes count]; i++)
|
||||
{
|
||||
type = [returnTypes objectAtIndex: i];
|
||||
if ([returnInfo member: type] != nil)
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (returnTypes == nil || [returnTypes count] == 0)
|
||||
{
|
||||
for (i = 0; i < [sendTypes count]; i++)
|
||||
{
|
||||
type = [sendTypes objectAtIndex: i];
|
||||
if ([combinations objectForKey: type] != nil)
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (i = 0; i < [sendTypes count]; i++)
|
||||
{
|
||||
NSSet *rset;
|
||||
|
||||
type = [sendTypes objectAtIndex: i];
|
||||
rset = [combinations objectForKey: type];
|
||||
if (rset != nil)
|
||||
{
|
||||
unsigned j;
|
||||
|
||||
for (j = 0; j < [returnTypes count]; j++)
|
||||
{
|
||||
type = [returnTypes objectAtIndex: j];
|
||||
if ([rset member: type] != nil)
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
/*
|
||||
* Use tag in menu cell to identify slot in menu titles array that
|
||||
* contains the full title of the service.
|
||||
* Return nil if this is not one of our service menu cells.
|
||||
*/
|
||||
- (NSString*) item2title: (NSCell*)item
|
||||
{
|
||||
unsigned pos;
|
||||
|
||||
if ([item target] != self)
|
||||
return nil;
|
||||
pos = [item tag];
|
||||
if (pos > [menuTitles count])
|
||||
return nil;
|
||||
return [menuTitles objectAtIndex: pos];
|
||||
}
|
||||
|
||||
|
||||
- (void) dealloc
|
||||
{
|
||||
NSRegisterServicesProvider(nil, nil);
|
||||
[languages release];
|
||||
[servicesProvider release];
|
||||
[returnInfo release];
|
||||
[combinations release];
|
||||
[title2info release];
|
||||
[menuTitles release];
|
||||
[servicesMenu release];
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- forward: (SEL)aSel :(arglist_t)frame
|
||||
{
|
||||
NSString *selName = NSStringFromSelector(aSel);
|
||||
|
||||
/*
|
||||
* If the selector matches the correct form for a services request,
|
||||
* send the message to the services provider - otherwise raise an
|
||||
* exception to say the method is not implemented.
|
||||
*/
|
||||
if ([selName hasSuffix: @":userData:error:"])
|
||||
return [servicesProvider performv: aSel :frame];
|
||||
else
|
||||
return [self notImplemented: aSel];
|
||||
}
|
||||
|
||||
- (void) rebuildServices
|
||||
{
|
||||
NSDictionary *services;
|
||||
NSUserDefaults *defs;
|
||||
NSMutableArray *newLang;
|
||||
NSSet *disabled;
|
||||
NSMutableSet *alreadyFound;
|
||||
NSMutableDictionary *newServices;
|
||||
unsigned pos;
|
||||
|
||||
/*
|
||||
* If the application has not yet started up fully and registered us,
|
||||
* we defer the rebuild intil we are registered. This avoids loads
|
||||
* of successive rebuilds as responder classes register the types they
|
||||
* can handle on startup.
|
||||
*/
|
||||
if (isRegistered == NO)
|
||||
return;
|
||||
|
||||
defs = [NSUserDefaults standardUserDefaults];
|
||||
newLang = [[[defs arrayForKey: @"Languages"] mutableCopy] autorelease];
|
||||
if (newLang == nil)
|
||||
{
|
||||
newLang = [NSMutableArray arrayWithCapacity: 1];
|
||||
}
|
||||
if ([newLang containsObject: @"default"] == NO)
|
||||
{
|
||||
[newLang addObject: @"default"];
|
||||
}
|
||||
ASSIGN(languages, newLang);
|
||||
|
||||
disabled = [NSSet setWithArray: [defs arrayForKey: @"DisabledServices"]];
|
||||
services = [GSAllServicesDictionary() objectForKey: @"ByService"];
|
||||
|
||||
newServices = [NSMutableDictionary dictionaryWithCapacity: 16];
|
||||
alreadyFound = [NSMutableSet setWithCapacity: 16];
|
||||
|
||||
/*
|
||||
* Build dictionary of services we can use.
|
||||
* 1. make sure we make dictionary keyed on preferred menu item language
|
||||
* 2. don't include entries for services already examined.
|
||||
* 3. don't include entries for menu items specifically disabled.
|
||||
* 4. don't include entries for which we have no registered types.
|
||||
*/
|
||||
for (pos = 0; pos < [languages count]; pos++)
|
||||
{
|
||||
NSDictionary *byLanguage;
|
||||
|
||||
byLanguage = [services objectForKey: [languages objectAtIndex: pos]];
|
||||
if (byLanguage != nil)
|
||||
{
|
||||
NSEnumerator *enumerator = [byLanguage keyEnumerator];
|
||||
NSString *menuItem;
|
||||
|
||||
while ((menuItem = [enumerator nextObject]) != nil)
|
||||
{
|
||||
NSDictionary *service = [byLanguage objectForKey: menuItem];
|
||||
|
||||
if ([alreadyFound member: service] != nil)
|
||||
continue;
|
||||
|
||||
[alreadyFound addObject: service];
|
||||
|
||||
if ([disabled member: menuItem] != nil)
|
||||
continue;
|
||||
|
||||
if ([self hasRegisteredTypes: service])
|
||||
[newServices setObject: service forKey: menuItem];
|
||||
}
|
||||
}
|
||||
}
|
||||
if ([newServices isEqual: title2info] == NO)
|
||||
{
|
||||
NSArray *titles;
|
||||
|
||||
ASSIGN(title2info, newServices);
|
||||
titles = [title2info allKeys];
|
||||
titles = [titles sortedArrayUsingSelector: @selector(compare:)];
|
||||
ASSIGN(menuTitles, titles);
|
||||
[self rebuildServicesMenu];
|
||||
}
|
||||
}
|
||||
|
||||
- (void) rebuildServicesMenu
|
||||
{
|
||||
if (isRegistered && servicesMenu)
|
||||
{
|
||||
NSArray *itemArray;
|
||||
NSMutableSet *keyEquivalents;
|
||||
unsigned pos;
|
||||
unsigned loc0;
|
||||
unsigned loc1;
|
||||
SEL sel = @selector(doService:);
|
||||
NSMenu *submenu = nil;
|
||||
|
||||
itemArray = [[servicesMenu itemArray] retain];
|
||||
pos = [itemArray count];
|
||||
while (pos > 0)
|
||||
{
|
||||
[servicesMenu removeItem: [itemArray objectAtIndex: --pos]];
|
||||
}
|
||||
[itemArray release];
|
||||
|
||||
keyEquivalents = [NSMutableSet setWithCapacity: 4];
|
||||
for (loc0 = pos = 0; pos < [menuTitles count]; pos++)
|
||||
{
|
||||
NSString *title = [menuTitles objectAtIndex: pos];
|
||||
NSString *equiv = @"";
|
||||
NSDictionary *info = [title2info objectForKey: title];
|
||||
NSDictionary *titles;
|
||||
NSDictionary *equivs;
|
||||
NSRange r;
|
||||
unsigned lang;
|
||||
id<NSMenuItem> item;
|
||||
|
||||
/*
|
||||
* Find the key equivalent corresponding to this menu title
|
||||
* in the service definition.
|
||||
*/
|
||||
titles = [info objectForKey: @"NSMenuItem"];
|
||||
equivs = [info objectForKey: @"NSKeyEquivalent"];
|
||||
for (lang = 0; lang < [languages count]; lang++)
|
||||
{
|
||||
NSString *language = [languages objectAtIndex: lang];
|
||||
NSString *t = [titles objectForKey: language];
|
||||
|
||||
if ([t isEqual: title])
|
||||
{
|
||||
equiv = [equivs objectForKey: language];
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Make a note that we are using the key equivalent, or
|
||||
* set to nil if we have already used it in this menu.
|
||||
*/
|
||||
if (equiv)
|
||||
{
|
||||
if ([keyEquivalents member: equiv] == nil)
|
||||
{
|
||||
[keyEquivalents addObject: equiv];
|
||||
}
|
||||
else
|
||||
{
|
||||
equiv = @"";
|
||||
}
|
||||
}
|
||||
|
||||
r = [title rangeOfString: @"/"];
|
||||
if (r.length > 0)
|
||||
{
|
||||
NSString *subtitle = [title substringFromIndex: r.location+1];
|
||||
NSString *parentTitle = [title substringToIndex: r.location];
|
||||
NSMenu *menu;
|
||||
|
||||
item = [servicesMenu itemWithTitle: parentTitle];
|
||||
if (item == nil)
|
||||
{
|
||||
loc1 = 0;
|
||||
item = [servicesMenu insertItemWithTitle: parentTitle
|
||||
action: 0
|
||||
keyEquivalent: @""
|
||||
atIndex: loc0++];
|
||||
menu = [[NSMenu alloc] initWithTitle: parentTitle];
|
||||
[servicesMenu setSubmenu: submenu
|
||||
forItem: item];
|
||||
}
|
||||
else
|
||||
{
|
||||
menu = (NSMenu*)[item target];
|
||||
}
|
||||
if (menu != submenu)
|
||||
{
|
||||
[submenu sizeToFit];
|
||||
submenu = menu;
|
||||
}
|
||||
item = [submenu insertItemWithTitle: subtitle
|
||||
action: sel
|
||||
keyEquivalent: equiv
|
||||
atIndex: loc1++];
|
||||
[item setTarget: self];
|
||||
[item setTag: pos];
|
||||
}
|
||||
else
|
||||
{
|
||||
item = [servicesMenu insertItemWithTitle: title
|
||||
action: sel
|
||||
keyEquivalent: equiv
|
||||
atIndex: loc0++];
|
||||
[item setTarget: self];
|
||||
[item setTag: pos];
|
||||
}
|
||||
}
|
||||
[submenu sizeToFit];
|
||||
[servicesMenu sizeToFit];
|
||||
[servicesMenu update];
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Set up connection to listen for incoming service requests.
|
||||
*/
|
||||
- (void) registerAsServiceProvider
|
||||
{
|
||||
if (isRegistered == NO)
|
||||
{
|
||||
NSString *appName;
|
||||
|
||||
isRegistered = YES;
|
||||
[self rebuildServices];
|
||||
appName = [[[NSProcessInfo processInfo] processName] lastPathComponent];
|
||||
NSRegisterServicesProvider(self, appName);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Register send and return types that an object can handle - we keep
|
||||
* a note of all the possible combinations -
|
||||
* 'returnInfo' is a set of all the return types that can be handled
|
||||
* without a send.
|
||||
* 'combinations' is a dictionary of all send types, with the assciated
|
||||
* values being sets of possible return types.
|
||||
*/
|
||||
- (void) registerSendTypes: (NSArray *)sendTypes
|
||||
returnTypes: (NSArray *)returnTypes
|
||||
{
|
||||
BOOL didChange = NO;
|
||||
unsigned i;
|
||||
|
||||
for (i = 0; i < [sendTypes count]; i++)
|
||||
{
|
||||
NSString *sendType = [sendTypes objectAtIndex: i];
|
||||
NSMutableSet *returnSet = [combinations objectForKey: sendType];
|
||||
|
||||
if (returnSet == nil)
|
||||
{
|
||||
returnSet = [NSMutableSet setWithCapacity: [returnTypes count]];
|
||||
[combinations setObject: returnSet forKey: sendType];
|
||||
[returnSet addObjectsFromArray: returnTypes];
|
||||
didChange = YES;
|
||||
}
|
||||
else
|
||||
{
|
||||
unsigned count = [returnSet count];
|
||||
|
||||
[returnSet addObjectsFromArray: returnTypes];
|
||||
if ([returnSet count] != count)
|
||||
{
|
||||
didChange = YES;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
i = [returnInfo count];
|
||||
[returnInfo addObjectsFromArray: returnTypes];
|
||||
if ([returnInfo count] != i)
|
||||
{
|
||||
didChange = YES;
|
||||
}
|
||||
|
||||
if (didChange)
|
||||
{
|
||||
[self rebuildServices];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSMenu*) servicesMenu
|
||||
{
|
||||
return servicesMenu;
|
||||
}
|
||||
|
||||
- (id) servicesProvider
|
||||
{
|
||||
return servicesProvider;
|
||||
}
|
||||
|
||||
- (void) setServicesMenu: (NSMenu*)aMenu
|
||||
{
|
||||
ASSIGN(servicesMenu, aMenu);
|
||||
[self rebuildServicesMenu];
|
||||
}
|
||||
|
||||
- (void) setServicesProvider: (id)anObject
|
||||
{
|
||||
ASSIGN(servicesProvider, anObject);
|
||||
}
|
||||
|
||||
- (BOOL) validateMenuItem: (NSCell*)item
|
||||
{
|
||||
NSString *title = [self item2title: item];
|
||||
NSDictionary *info = [title2info objectForKey: title];
|
||||
NSArray *sendTypes = [info objectForKey: @"NSSendTypes"];
|
||||
NSArray *returnTypes = [info objectForKey: @"NSReturnTypes"];
|
||||
unsigned i, j;
|
||||
unsigned es = [sendTypes count];
|
||||
unsigned er = [returnTypes count];
|
||||
NSWindow *resp = [[application keyWindow] firstResponder];
|
||||
|
||||
/*
|
||||
* If the menu item is not in our map, it must be the cell containing
|
||||
* a sub-menu - so we see if any cell in the submenu is valid.
|
||||
*/
|
||||
if (title == nil)
|
||||
{
|
||||
NSMenu *sub = [item target];
|
||||
|
||||
if (sub && [sub isKindOfClass: [NSMenu class]])
|
||||
{
|
||||
NSArray *a = [sub itemArray];
|
||||
|
||||
for (i = 0; i < [a count]; i++)
|
||||
{
|
||||
if ([self validateMenuItem: [a objectAtIndex: i]] == YES)
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
/*
|
||||
* The cell corresponds to one of our services - so we check to see if
|
||||
* there is anything that can deal with it.
|
||||
*/
|
||||
if (es == 0)
|
||||
{
|
||||
if (er == 0)
|
||||
{
|
||||
if ([resp validRequestorForSendType: nil
|
||||
returnType: nil] != nil)
|
||||
return YES;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (j = 0; j < er; j++)
|
||||
{
|
||||
NSString *returnType;
|
||||
|
||||
returnType = [returnTypes objectAtIndex: j];
|
||||
if ([resp validRequestorForSendType: nil
|
||||
returnType: returnType] != nil)
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (i = 0; i < es; i++)
|
||||
{
|
||||
NSString *sendType;
|
||||
|
||||
sendType = [sendTypes objectAtIndex: i];
|
||||
|
||||
if (er == 0)
|
||||
{
|
||||
if ([resp validRequestorForSendType: sendType
|
||||
returnType: nil] != nil)
|
||||
return YES;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (j = 0; j < er; j++)
|
||||
{
|
||||
NSString *returnType;
|
||||
|
||||
returnType = [returnTypes objectAtIndex: j];
|
||||
if ([resp validRequestorForSendType: sendType
|
||||
returnType: returnType] != nil)
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void) updateServicesMenu
|
||||
{
|
||||
if (servicesMenu && [[application mainMenu] autoenablesItems])
|
||||
{
|
||||
NSMenuMatrix *menuCells;
|
||||
NSArray *a;
|
||||
unsigned i;
|
||||
NSMenu *mainMenu = [application mainMenu];
|
||||
BOOL found = NO;
|
||||
|
||||
a = [mainMenu itemArray];
|
||||
for (i = 0; i < [a count]; i++)
|
||||
if ([[a objectAtIndex: i] target] == servicesMenu)
|
||||
found = YES;
|
||||
if (found == NO)
|
||||
{
|
||||
NSLog(@"Services menu not in main menu!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
menuCells = [servicesMenu menuCells];
|
||||
a = [menuCells itemArray];
|
||||
|
||||
for (i = 0; i < [a count]; i++)
|
||||
{
|
||||
NSCell *cell = [a objectAtIndex: i];
|
||||
BOOL wasEnabled = [cell isEnabled];
|
||||
BOOL shouldBeEnabled = [self validateMenuItem: cell];
|
||||
|
||||
if (wasEnabled != shouldBeEnabled)
|
||||
{
|
||||
[cell setEnabled: shouldBeEnabled];
|
||||
[menuCells setNeedsDisplayInRect: [menuCells cellFrameAtRow: i]];
|
||||
}
|
||||
}
|
||||
/* FIXME - only doing this here 'cos auto-display doesn't work */
|
||||
if ([menuCells needsDisplay])
|
||||
[menuCells display];
|
||||
}
|
||||
}
|
||||
|
||||
@end /* ApplicationListener */
|
||||
#include "GNUServicesManager.h"
|
||||
|
||||
//*****************************************************************************
|
||||
//
|
||||
|
@ -754,7 +120,7 @@ static id NSApp;
|
|||
|
||||
NSDebugLog(@"Begin of NSApplication -init\n");
|
||||
|
||||
listener = [ApplicationListener newWithApplication: self];
|
||||
listener = [GNUServicesManager newWithApplication: self];
|
||||
window_list = [NSMutableArray new]; // allocate window list
|
||||
window_count = 1;
|
||||
|
||||
|
@ -1609,7 +975,7 @@ int i;
|
|||
returnTypes: (NSArray *)returnTypes
|
||||
{
|
||||
[listener registerSendTypes: sendTypes
|
||||
returnTypes: returnTypes];
|
||||
returnTypes: returnTypes];
|
||||
}
|
||||
|
||||
- (NSMenu *) servicesMenu
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue