[squeak-dev] Squeak 4.5: Services-Base-nice.50.mcz

commits at source.squeak.org commits at source.squeak.org
Fri Jan 24 20:11:42 UTC 2014


Chris Muller uploaded a new version of Services-Base to project Squeak 4.5:
http://source.squeak.org/squeak45/Services-Base-nice.50.mcz

==================== Summary ====================

Name: Services-Base-nice.50
Author: nice
Time: 18 December 2013, 2:59:13.287 pm
UUID: 963e5ed8-8baa-425b-a73b-131d52bf202d
Ancestors: Services-Base-nice.49

Classify some as yet unclassified.

==================== Snapshot ====================

SystemOrganization addCategory: #'Services-Base'!
SystemOrganization addCategory: #'Services-Base-Providers'!
SystemOrganization addCategory: #'Services-Base-Requestors'!
SystemOrganization addCategory: #'Services-Base-GUI'!

Preferences subclass: #ServicePreferences
	instanceVariableNames: ''
	classVariableNames: 'ServiceDictionaryOfPreferences'
	poolDictionaries: ''
	category: 'Services-Base-GUI'!

!ServicePreferences commentStamp: 'rr 7/10/2006 15:36' prior: 0!
I store the preferences related to the servicse framework. The preferences are editable via the Services Browser, based on Hernan Tylim's Preference Browser.

The main preference categories for services are: 

-- keyboard shortcuts -- : several text preferences, one per keyboard shortcuts. To edit them,  enter a service identifier (equal to the method name under which it is defined in its ServiceProvider), and accept with alt-s or enter

-- menu contents -- : All the service categories in the image have a text preference under here. To edit it, enter the services identifiers you wish to put in this category, separating them with a single space character. The order is important: it defines the order of the items in menus.

-- settings -- : general boolean preferences.

Then there is a preference category for each provider in the image. Under each, you will find:
A boolean preference for each service in the image. If it is false, the service will not appear in menus. 
The text preference for each service category defined by the service provider. This is the same as the one appearing in the menu contents preference category.!

----- Method: ServicePreferences class>>compileAccessMethodForPreference: (in category 'accessing') -----
compileAccessMethodForPreference: aPreference
	"do nothing"!

----- Method: ServicePreferences class>>dictionaryOfPreferences (in category 'accessing') -----
dictionaryOfPreferences
	ServiceDictionaryOfPreferences 
		ifNil: [ServiceDictionaryOfPreferences := IdentityDictionary new].
	^ ServiceDictionaryOfPreferences !

----- Method: ServicePreferences class>>dictionaryOfPreferences: (in category 'accessing') -----
dictionaryOfPreferences: aDictionary
	ServiceDictionaryOfPreferences := aDictionary!

----- Method: ServicePreferences class>>replayPreferences: (in category 'replaying') -----
replayPreferences: preferences 
	| s |
	s := SortedCollection new
				sortBlock: [:a :b | a last < b last].
	s addAll: preferences;
		 reSort.
	s
		do: [:e | | v | 
			v := self valueOfPreference: e first ifAbsent: ''.
			self setPreference: e first toValue: (v
					ifEmpty: ['']
					ifNotEmpty: [v , ' '])
					, e second]!

----- Method: ServicePreferences class>>wipe (in category 'accessing') -----
wipe
	self dictionaryOfPreferences: nil!

----- Method: PasteUpMorph>>openWorldMenu (in category '*services-base') -----
openWorldMenu
	| menu |
	menu := (TheWorldMenu new adaptToWorld: self) buildWorldMenu.
	menu addTitle: Preferences desktopMenuTitle translated.
	menu openInHand!

----- Method: PasteUpMorph>>requestor (in category '*services-base') -----
requestor
	"returns the focused window's requestor"
	^ Requestor default!

----- Method: PasteUpMorph>>topRequestor (in category '*services-base') -----
topRequestor
	"returns the focused window's requestor"
	^ SystemWindow topWindow requestor!

----- Method: PasteUpMorph>>worldMenu (in category '*services-base') -----
worldMenu
	^ TheWorldMenu new adaptToWorld: self!

----- Method: Model>>requestor (in category '*services-base') -----
requestor
	^ Requestor default!

----- Method: StringHolder>>codeTextMorph (in category '*services-base') -----
codeTextMorph
	^ self dependents
		detect: [:dep | (dep isKindOf: PluggableTextMorph)
				and: [dep getTextSelector == #contents]]
		ifNone: []!

----- Method: StringHolder>>requestor (in category '*services-base') -----
requestor
	^ (TextRequestor new) model: self; yourself!

----- Method: StringHolder>>selectedInterval (in category '*services-base') -----
selectedInterval
	^self codeTextMorph selectionInterval!

----- Method: Collection>>chooseOne: (in category '*services-base') -----
chooseOne: caption 
	"pops up a menu asking for one of the elements in the collection.
	If none is chosen, raises a ServiceCancelled notification"

	| m |
	m := MenuMorph entitled: caption.
	self do: 
			[:ea | 
			m 
				add: ea
				target: [:n | ^ n]
				selector: #value:
				argument: ea].
	m invokeModal.
	ServiceCancelled signal!

----- Method: PreferenceBrowserMorph class>>updateBrowsers (in category '*services-base') -----
updateBrowsers

	(self allInstances select: [:e | e visible]) 
		do: [:each | 
			(each  findDeepSubmorphThat:[:m | m  isKindOf:PluggableListMorph]
				ifAbsent:[^ self]) verifyContents].!

PreferenceBrowserMorph subclass: #ServiceBrowserMorph
	instanceVariableNames: ''
	classVariableNames: ''
	poolDictionaries: ''
	category: 'Services-Base-GUI'!

!ServiceBrowserMorph commentStamp: 'rr 7/10/2006 15:28' prior: 0!
I subclass the PreferenceBrowserMorph to adapt the interface to services. So far the changes are minimal.!

----- Method: ServiceBrowserMorph>>newButtonRow (in category 'as yet unclassified') -----
newButtonRow
	^BorderedMorph new
		color: Color transparent; 
		cellInset: 2;
		layoutInset: 2;
		layoutPolicy: TableLayout new;
		listDirection: #leftToRight;
		listCentering: #topLeft;
		cellPositioning: #topLeft;
		on: #mouseEnter send: #paneTransition: to: self;
		on: #mouseLeave send: #paneTransition: to: self;
		"addMorphBack: self defaultButton;
		addMorphBack: self newSeparator;
		addMorphBack: self saveButton;
		addMorphBack: self loadButton;
		addMorphBack: self newSeparator;
		addMorphBack: self saveToDiskButton;
		addMorphBack: self loadFromDiskButton;
		addMorphBack: self newSeparator;
		addMorphBack: self newTransparentFiller;
		addMorphBack: self helpButton;"
		yourself.!

----- Method: SystemWindow class>>topWindow (in category '*services-base') -----
topWindow
	^ TopWindow!

----- Method: SystemWindow>>requestor (in category '*services-base') -----
requestor
	^[model requestor] 
		on: Error 
		do: [Transcript show: 'no requestor for : ', model class name. Requestor default] !

----- Method: SystemWindow>>topWindow (in category '*services-base') -----
topWindow
	^ TopWindow!

----- Method: BlockContext>>valueWithRequestor: (in category '*services-base') -----
valueWithRequestor: aRequestor 
	"To do later: make the fillInTheBlank display more informative captions.
	Include the description of the service, and maybe record steps"

	^ self numArgs isZero 
		ifTrue: [self value]
		ifFalse: [self value: aRequestor]!

----- Method: Association>>serviceUpdate (in category '*services-base-preferences') -----
serviceUpdate
	self key service perform: self value!

----- Method: PreferenceBrowser class>>openForServices (in category '*services-base') -----
openForServices
	"PreferenceBrowser openForServices"
	| browser |
	browser := self new.
	browser initializeForServices.
	(ServiceBrowserMorph withModel: browser)
		openInWorld.
	^browser.	!

----- Method: PreferenceBrowser>>initializeForServices (in category '*services-base') -----
initializeForServices
	preferences := ServicePreferences.
	title := 'Services Browser'!

----- Method: MessageSet>>browseReference: (in category '*services-base') -----
browseReference: ref
	self okToChange ifTrue: [
	self initializeMessageList: (OrderedCollection with: ref).
	self changed: #messageList.
	self messageListIndex: 1.
	] !

----- Method: MessageSet>>selectReference: (in category '*services-base') -----
selectReference: ref
	self okToChange ifTrue: [self messageListIndex: (self messageList indexOf: ref)]!

----- Method: String>>service (in category '*services-base') -----
service
	^ self serviceOrNil ifNil: [ServiceCategory new id: self asSymbol]!

----- Method: String>>serviceOrNil (in category '*services-base') -----
serviceOrNil
	^ ServiceRegistry current serviceWithId: self asSymbol!

Object subclass: #BasicRequestor
	instanceVariableNames: 'caption answer'
	classVariableNames: ''
	poolDictionaries: ''
	category: 'Services-Base'!

!BasicRequestor commentStamp: 'rr 7/10/2006 14:44' prior: 0!
This class is the root of the Requestor hierarchy.

Requestors are interfaces between services and the system. ServiceActions are given an instance
of a Requestor, and they ask it for the data they need. The requestor is determined by the model of the application. A class used as a model can implement the #requestor message to return the most suited requestor. A requestor knows how to query its model and the user if needed.

Requestor are defined in hierarchies so that the protocol they rely on (methods starting with 'get') can be easily reused.!

----- Method: BasicRequestor>>caption: (in category 'generic requests') -----
caption: aString
	caption := aString!

----- Method: BasicRequestor>>get: (in category 'executing') -----
get: aString
	self caption: aString.
	^ self getSymbol!

----- Method: BasicRequestor>>getString (in category 'generic requests') -----
getString
	| result |
	result := UIManager default  request:caption  initialAnswer:answer contents.
	self newCaption.
	result isEmpty  |result isNil  ifTrue:[ServiceCancelled signal].
	^ result!

----- Method: BasicRequestor>>getStringCollection (in category 'generic requests') -----
getStringCollection
	caption := caption, Character cr asString, 'Separate items with space'.
	^ (self getString findTokens: ' ') collect: [:each | each copyWithoutAll: ' ' ]!

----- Method: BasicRequestor>>getSymbol (in category 'generic requests') -----
getSymbol
	^ self getString asSymbol!

----- Method: BasicRequestor>>getSymbolCollection (in category 'generic requests') -----
getSymbolCollection
	^[self getStringCollection collect: [:each | each asSymbol]] 
		on: ServiceCancelled
		do: [#()]!

----- Method: BasicRequestor>>initialize (in category 'initialize-release') -----
initialize
	self newCaption!

----- Method: BasicRequestor>>newCaption (in category 'generic requests') -----
newCaption
	caption := 'Enter text'.
	answer := String new writeStream.!

BasicRequestor subclass: #Requestor
	instanceVariableNames: ''
	classVariableNames: ''
	poolDictionaries: ''
	category: 'Services-Base-Requestors'!

!Requestor commentStamp: 'rr 7/10/2006 15:19' prior: 0!
I am an implementation of BasicRequestor with some requests already implemented.!

----- Method: Requestor class>>default (in category 'as yet unclassified') -----
default
	"returns a default requestor"
	^ self new!

----- Method: Requestor>>getClass (in category 'requests') -----
getClass
	^Smalltalk at: self getSymbol!

----- Method: Requestor>>getClassCollection (in category 'requests') -----
getClassCollection
	^ self getSymbolCollection collect: [:className | Smalltalk at: className]!

----- Method: Requestor>>getMethodBody (in category 'requests') -----
getMethodBody
	| m |
	m := FillInTheBlankMorph new.
	m setQuery: 'Please enter the full body of the method you want to define' 
		initialAnswer:  self class sourceCodeTemplate
		answerExtent: 500 at 250
		acceptOnCR: false. 
	World addMorph: m centeredNear: World activeHand position.
	^ m getUserResponse.!

----- Method: Requestor>>getSelection (in category 'requests') -----
getSelection
	"Sorry to feedle with fillInTheBlankMorph innards, but I had to"
	| text m |
	text := (MethodReference class: self getClass selector: self getSelector) sourceCode.
	m := FillInTheBlankMorph new.
	m setQuery: 'Highlight a part of the source code, and accept' initialAnswer: text
		answerExtent: 500 at 250
		acceptOnCR: true. 
	World addMorph: m centeredNear: World activeHand position.
	m getUserResponse.
	^ m selection!

----- Method: Requestor>>getSelector (in category 'services requests') -----
getSelector
	^ self caption: 'enter selector'; getSymbol!

Requestor subclass: #TextRequestor
	instanceVariableNames: 'model'
	classVariableNames: ''
	poolDictionaries: ''
	category: 'Services-Base-Requestors'!

!TextRequestor commentStamp: 'rr 7/10/2006 15:20' prior: 0!
A requestor for text areas, able for example to fetch the current selected text.!

TextRequestor subclass: #BrowserRequestor
	instanceVariableNames: ''
	classVariableNames: ''
	poolDictionaries: ''
	category: 'Services-Base-Requestors'!

!BrowserRequestor commentStamp: 'rr 7/10/2006 15:24' prior: 0!
I am a requestor specialized to fetch information in a Browser.
I can ask a browser its selected class and selected method for example.
If the RB is installed too, I can also fetch ast nodes in the browser's selected
method.

I am the default requestor for CodeHolder and it's subclasses.

To be integrated with services, alternative browsers, such as the OmniBrowser and Whisker should define a specialized requestor subclassing this one. A few core messages would need to be redefined, such as getClass, getMessage ... to be adapted to the browser's data structures. 
Only a few of them have to be overridden, the majority of the requests rely on a few base ones.!

----- Method: BrowserRequestor>>browser: (in category 'initialize-release') -----
browser: b
	self model: b!

----- Method: BrowserRequestor>>getBrowser (in category 'requests') -----
getBrowser
	^ self getModel!

----- Method: BrowserRequestor>>getClass (in category 'requests') -----
getClass
	^ self getBrowser selectedClassOrMetaClass!

----- Method: BrowserRequestor>>getInitializingExpressionForTheNewParameter (in category 'requests') -----
getInitializingExpressionForTheNewParameter
	^ UIManager default request: 'enter default parameter code'
				 initialAnswer: '42'!

----- Method: BrowserRequestor>>getNewSelectorName (in category 'requests') -----
getNewSelectorName
	^ UIManager default  request: 'enter the new selector name'
					initialAnswer: self getSelector!

----- Method: BrowserRequestor>>getNewVariableName (in category 'requests') -----
getNewVariableName
	^ UIManager default request: 'Enter the new variable name' translated initialAnswer: 'foo'!

----- Method: BrowserRequestor>>getPackage (in category 'requests') -----
getPackage
	self getSelector ifNil: [
			^ PackageInfo named:(
					self getClass ifNil: [self getSystemCategory] 
									ifNotNil: [:c | c category copyUpTo:  $-])].
	^ PackageOrganizer default 
			packageOfMethod: 
					(MethodReference class: self getClass
										selector: self getSelector)
			ifNone: [PackageInfo named: (self getClass category copyUpTo:  $-)] !

----- Method: BrowserRequestor>>getPackageForCategory (in category 'requests') -----
getPackageForCategory
	"answers a packageinfo for the current class category"
	^ PackageInfo named: self getClass category!

----- Method: BrowserRequestor>>getPackageForCategoryName (in category 'requests') -----
getPackageForCategoryName
	"answers a packageinfo for the current class category"
	^  self getPackageForCategory packageName!

----- Method: BrowserRequestor>>getPackageName (in category 'requests') -----
getPackageName
	^ self getPackage packageName!

----- Method: BrowserRequestor>>getPackageProvider (in category 'requests') -----
getPackageProvider
	| provs classes |
	provs := ServiceProvider registeredProviders.
	classes := self getPackage classes.
	^ classes detect: [:e | provs includes: e] ifNone: [ServiceProvider newProviderFor: self getPackageName]!

----- Method: BrowserRequestor>>getSelection (in category 'requests') -----
getSelection
	self getBrowser selectedInterval ifEmpty: [^super getSelection].
	^ self getBrowser selectedInterval!

----- Method: BrowserRequestor>>getSelector (in category 'requests') -----
getSelector
	| s |
	s := self getBrowser selectedMessageName.
	^ s ifNil: [super getSelector] ifNotNil: [s]!

----- Method: BrowserRequestor>>getSelectorCollection (in category 'requests') -----
getSelectorCollection
	self caption: 'enter selector list'.
	^ self getSymbolCollection !

----- Method: BrowserRequestor>>getSelectorName (in category 'requests') -----
getSelectorName
	^ self getBrowser selectedMessageName!

----- Method: BrowserRequestor>>getSystemCategory (in category 'requests') -----
getSystemCategory
	^ self getBrowser selectedSystemCategory!

----- Method: TextRequestor>>getCurrentText (in category 'request') -----
getCurrentText
	"returns the unnacepted text in the text morph" 
	^ self getModel codeTextMorph text!

----- Method: TextRequestor>>getModel (in category 'request') -----
getModel
	^ model first!

----- Method: TextRequestor>>model: (in category 'accessing') -----
model: aModel
	model := WeakArray with: aModel!

----- Method: Object>>requestor (in category '*services-base') -----
requestor
	"returns the focused window's requestor"

	"SystemWindow focusedWindow ifNotNilDo: [:w | ^ w requestor]."

	"triggers an infinite loop"

	^ Requestor default!

Object subclass: #ServiceAction
	instanceVariableNames: 'condition action requestor label shortLabel description id provider enabled'
	classVariableNames: ''
	poolDictionaries: ''
	category: 'Services-Base'!

!ServiceAction commentStamp: 'rr 7/10/2006 14:58' prior: 0!
ServiceAction are executable objects in various contexts.
They can be displayed as buttons or menu items or bounded to keyboard shortcuts.

ServiceActions are defined in methods in an instance of a ServiceProvider class (in the 'services' method category), using the following template:

serviceIdentifierAndMethodName
	^ ServiceAction
		text: 'Menu item text'
		button: 'Button text'
		description: 'Longer text that appears in help balloons'
		action: [:r | "Code block fetching data from the requestor instance, r, that is passed to the block"]
		
or, alternatively:

serviceIdentifierAndMethodName
	^ ServiceAction
		text: 'Menu item text'
		button: 'Button text'
		description: 'Longer text that appears in help balloons'
		action: [:r | "Code block fetching data from the requestor instance, r, that is passed to the block"]
		condition: [:r | "second block returning true if the service can be used at the time being, false otherwise. Data can still be fetched from the requestor instance"]
		
The method name in which the service is defined becomes its identifier. To build the hierarchy of services and to assign them to shortcuts, you will need to type this names in the relevant fields of the Services Browser.
		
Services are arranged in a hierarchy. and bound to keyboard shortcuts using the ServicesBrowser.
!

----- Method: ServiceAction class>>id:text:button:description:action: (in category 'instance creation') -----
id: aSymbol text: aStringOrBlock button: buttonString description: aString action: aBlock
	^ self id: aSymbol 
		text: aStringOrBlock 
		button: buttonString 
		description: aString 
		action: aBlock 
		condition: [:r | true]!

----- Method: ServiceAction class>>id:text:button:description:action:condition: (in category 'instance creation') -----
id: aSymbol text: aStringOrBlock button: buttonString description: aString action: aBlock condition: cBlock
	^ (self new) 
		id: aSymbol;
		text: aStringOrBlock; 
		buttonLabel: buttonString; 
		description: aString; 
		action: aBlock;
		condition: cBlock;
		yourself!

----- Method: ServiceAction class>>initialize (in category 'class initialization') -----
initialize
	#(
	(inlineServicesInMenu true 'Inline the services the squeak menus') 
	(useOnlyServicesInMenu false 'Use only services and not regular menu items')
	(useServicesInBrowserButtonBar false 'Use a service-based button bar')) 
		do: [:tr |
				Preferences 
						addPreference: tr first
						categories: #(#services)
						default: tr second
						balloonHelp: tr third] 
	!

----- Method: ServiceAction class>>text:button:description:action: (in category 'instance creation') -----
text: aStringOrBlock button: buttonString description: aString action: aBlock
	"use when id can be automatically generated"
	^ self id: nil 
		text: aStringOrBlock 
		button: buttonString 
		description: aString 
		action: aBlock 
		condition: [:r | true]!

----- Method: ServiceAction class>>text:button:description:action:condition: (in category 'instance creation') -----
text: aStringOrBlock button: buttonString description: aString action: aBlock condition: cBlock
	"use when id can be generated"
	^ self 
		id: nil 
		text: aStringOrBlock 
		button: buttonString 
		description: aString 
		action: aBlock
		condition: cBlock!

----- Method: ServiceAction class>>text:description:action: (in category 'instance creation') -----
text: textString description: aString action: aBlock
	"use when id can be generated"
	^ self id: nil text: textString button: textString description: aString action: aBlock!

----- Method: ServiceAction>>action: (in category 'accessing') -----
action: aBlock
	action := aBlock!

----- Method: ServiceAction>>addPreference:category:selector: (in category 'preferences') -----
addPreference: name category: cat selector: sel
	
	ServicePreferences 
		 addPreference: name
		 categories: {cat asSymbol. self providerCategory}
		 default: ''
		 balloonHelp:self description
		 projectLocal:false
		 changeInformee: self id -> sel
		 changeSelector: #serviceUpdate
		type: #String!

----- Method: ServiceAction>>buttonLabel (in category 'accessing') -----
buttonLabel
	^ shortLabel
		ifNil: [self text]
		ifNotNil: [shortLabel ifEmpty: [self text] ifNotEmpty: [shortLabel]]!

----- Method: ServiceAction>>buttonLabel: (in category 'accessing') -----
buttonLabel: anObject
	shortLabel := anObject!

----- Method: ServiceAction>>categories (in category 'accessing') -----
categories
	^ ServiceRegistry current categories select: [:e | e services includes: self]!

----- Method: ServiceAction>>condExecuteWith: (in category 'executing') -----
condExecuteWith: aRequestor
	self requestor: aRequestor.
	self executeCondition 
			ifTrue: [self execute] 
			ifFalse: [Beeper beep]!

----- Method: ServiceAction>>condition: (in category 'accessing') -----
condition: aBlock
	condition := aBlock!

----- Method: ServiceAction>>description (in category 'accessing') -----
description

	^ description ifNil: [self text] ifNotNil: [description]!

----- Method: ServiceAction>>description: (in category 'accessing') -----
description: anObject
	description := anObject select: [:each | (each = Character cr) not] 
						thenCollect: [:each | each = Character tab ifTrue: [Character space]
															ifFalse: [each]].!

----- Method: ServiceAction>>execute (in category 'executing') -----
execute
	^ action clone valueWithRequestor: World topRequestor!

----- Method: ServiceAction>>executeCondition (in category 'executing') -----
executeCondition
	^ [condition clone valueWithRequestor: World topRequestor]
		on: Error
		do: [false]!

----- Method: ServiceAction>>id (in category 'accessing') -----
id
	^id!

----- Method: ServiceAction>>id: (in category 'accessing') -----
id: aSymbol
	id := aSymbol!

----- Method: ServiceAction>>initialize (in category 'initialize-release') -----
initialize
	self
		action: [].
	self
		condition: [true].
	self text: 'no op'.
	self requestor: Requestor new.
	self id: #none.
	enabled := true!

----- Method: ServiceAction>>insertPreferences (in category 'preferences') -----
insertPreferences
	ServicePreferences
		addPreference: self id
		categories: (Array with: self providerCategory)
		default: true
		balloonHelp: self description
		projectLocal: false
		changeInformee: self id -> #updateEnable
		changeSelector: #serviceUpdate
		type: #Boolean!

----- Method: ServiceAction>>isCategory (in category 'testing') -----
isCategory
	^ false!

----- Method: ServiceAction>>isEnabled (in category 'testing') -----
isEnabled
	^ enabled!

----- Method: ServiceAction>>menuLabel (in category 'accessing') -----
menuLabel
	| l sh |
	l := self text.
	l size > 50 ifTrue: [l := (l first: 47), '...'].	
	sh := self shortcut.
	sh := (sh isNil or: [sh isEmpty]) ifTrue: [''] ifFalse: [' (', sh, ')'].
	^ l capitalized, sh!

----- Method: ServiceAction>>menuLabelNumbered: (in category 'accessing') -----
menuLabelNumbered: i
	| l sh str |
	l := self text.
	l size > 50
		ifTrue: [l := (l first: 47)
						, '...'].
	sh := self shortcut.
	sh := (sh isNil
					or: [sh isEmpty])
				ifTrue: ['']
				ifFalse: [' (' , sh , ')'].
	str := i isZero ifTrue: [''] ifFalse: [i asString, '. '].
	^ str, l capitalized , sh!

----- Method: ServiceAction>>perform:orSendTo: (in category 'executing') -----
perform: selector orSendTo: otherTarget
	^ self perform: selector!

----- Method: ServiceAction>>preferences (in category 'preferences') -----
preferences
	^ {ServicePreferences preferenceAt: self shortcutPreference} select: [:e | e notNil]!

----- Method: ServiceAction>>printOn: (in category 'printing') -----
printOn: aStream 
	super printOn: aStream.
	aStream
		 space ;
		 nextPutAll: id asString!

----- Method: ServiceAction>>provider (in category 'accessing') -----
provider
	^ provider
		ifNil: [nil]
		ifNotNil: [provider new]!

----- Method: ServiceAction>>provider: (in category 'accessing') -----
provider: p
	provider := p!

----- Method: ServiceAction>>providerCategory (in category 'preferences') -----
providerCategory
	^ provider name!

----- Method: ServiceAction>>requestor (in category 'accessing') -----
requestor
	^requestor!

----- Method: ServiceAction>>requestor: (in category 'accessing') -----
requestor: anObject
	requestor := anObject!

----- Method: ServiceAction>>shortcut (in category 'preferences') -----
shortcut
	^ ServicePreferences valueOfPreference: self shortcutPreference!

----- Method: ServiceAction>>shortcutPreference (in category 'preferences') -----
shortcutPreference 
		^ ('Shortcut for ', self id, ':') asSymbol!

----- Method: ServiceAction>>text (in category 'accessing') -----
text
	^label isBlock ifTrue: [label  value: requestor] ifFalse: [label]!

----- Method: ServiceAction>>text: (in category 'accessing') -----
text: aString
	label := aString!

----- Method: ServiceAction>>updateEnable (in category 'preferences') -----
updateEnable
	enabled := ServicePreferences
				valueOfPreference: self id
				ifAbsent: [true]!

----- Method: ServiceAction>>updateShortcut (in category 'updating') -----
updateShortcut
	(self systemNavigation allImplementorsOf: #processService:newShortcut:) 
		do: [:ref | | cls |
			cls := ref actualClass.
			cls isMeta ifTrue: [cls soleInstance processService: self newShortcut: self shortcut]].
	ServiceRegistry ifInteractiveDo: [self provider savePreferencesFor: self]!

ServiceAction subclass: #ServiceCategory
	instanceVariableNames: 'services'
	classVariableNames: ''
	poolDictionaries: ''
	category: 'Services-Base'!

!ServiceCategory commentStamp: 'rr 7/10/2006 15:06' prior: 0!
I represent a category of services that can be added to a menu.
I can be displayed as a menu or button bar containing my services.
I am also a subclass of ServiceAction, so I can form a subcategory of another service category.

Like services, I am created in methods of a ServiceProvider, in the 'services' method protocol.
The template to create a service category is the following:

methodNameAndServiceCategoryId
	^ ServiceCategory 
		text: 'Menu text' 
		button: 'Button  text' 
		description: 'Longer descriptive text appearing in help balloons'
	
To put services in a service category, you have to use the Service Browser, located in the word menu, under the 'Preferences and Services' menu item.
In it, you can look up for the name of your category, and enter service identifiers as children
of the category in the associatedd text field, separating them with spaces.!

----- Method: ServiceCategory class>>text:button:description: (in category 'instance creation') -----
text: aStringOrBlock button: buttonString description: aString
	"use when id can be generated"
	^ self id: nil text: aStringOrBlock button: buttonString description: aString action: [] !

----- Method: ServiceCategory>>childrenPreferences (in category 'preferences') -----
childrenPreferences
		^ ('Items in ', self id, ':') asSymbol!

----- Method: ServiceCategory>>enabledServices (in category 'accessing') -----
enabledServices
	^ services
		select: [:e | e isEnabled]!

----- Method: ServiceCategory>>execute (in category 'executing') -----
execute
	"displays the subservices as a submenu"
	ServiceGui openMenuFor: self!

----- Method: ServiceCategory>>externalPreferences (in category 'preferences') -----
externalPreferences
	| p |
	p := ServicePreferences valueOfPreference: self childrenPreferences ifAbsent: [''].
	^ (p findTokens: ' ') collect: [:e | e service]!

----- Method: ServiceCategory>>initialize (in category 'initialize-release') -----
initialize

	services := OrderedCollection new.
	super initialize.
	
!

----- Method: ServiceCategory>>insertPreferences (in category 'preferences') -----
insertPreferences
	super insertPreferences.
	ServicePreferences 
		addPreference: self childrenPreferences
		categories: { 
				(#'-- menu contents --').
				(self providerCategory)}
		default: ''
		balloonHelp: self description
		projectLocal: false
		changeInformee: self id -> #updateChildren
		changeSelector: #serviceUpdate
		type: #String!

----- Method: ServiceCategory>>isCategory (in category 'testing') -----
isCategory
	^ true!

----- Method: ServiceCategory>>newChildren (in category 'preferences') -----
newChildren
	| s |
	s := ServicePreferences valueOfPreference: self childrenPreferences.
	^ (s findTokens: ' ') collect: [:str | str serviceOrNil]!

----- Method: ServiceCategory>>newChildrenValid (in category 'preferences') -----
newChildrenValid
	| s |
	s := ServicePreferences valueOfPreference: self childrenPreferences.
	^ (s findTokens: ' ') allSatisfy: [:str | 
		str serviceOrNil 
			ifNil: [ServiceRegistry ifInteractiveDo: 
						[self inform: str, ' is not a valid service name']. 
					false]
			ifNotNil: [true]]!

----- Method: ServiceCategory>>prefServices (in category 'preferences') -----
prefServices
	| s |
	s := ServicePreferences valueOfPreference: self childrenPreferences.
	^ (s findTokens: ' ') collect: [:str | str service]!

----- Method: ServiceCategory>>replaceChildren (in category 'preferences') -----
replaceChildren
	ServiceRegistry ifInteractiveDo: [services
		do: [:s | s provider
				ifNotNil: [:p | p class removeSelector: (self id , s id) asSymbol]]].
	services := self newChildren.
	services
		do: [:e | 
			(ServicePreferences preferenceAt: e shortcutPreference)
				ifNotNil: [:p | p categoryList: {'-- keyboard shortcuts --'. self id asString}].
			ServiceRegistry
				ifInteractiveDo: [self provider savePreferencesFor: self]]!

----- Method: ServiceCategory>>requestor: (in category 'accessing') -----
requestor: aRequestor
	super requestor: aRequestor.
	self services do: [:s | s requestor: aRequestor]!

----- Method: ServiceCategory>>services (in category 'accessing') -----
services
	^services!

----- Method: ServiceCategory>>updateChildren (in category 'preferences') -----
updateChildren

	self newChildrenValid
		ifTrue: [self replaceChildren].
	"PreferenceBrowserMorph updateBrowsers."
	ServiceGui updateBar: self!

Object subclass: #ServiceGui
	instanceVariableNames: 'menu bar service n'
	classVariableNames: ''
	poolDictionaries: ''
	category: 'Services-Base-GUI'!
ServiceGui class
	instanceVariableNames: 'bars'!

!ServiceGui commentStamp: 'rr 7/10/2006 15:29' prior: 0!
I abstract all the UI-related behaviors for the services framework.
In the future I could be changed to be compatible with ToolBuilder!
ServiceGui class
	instanceVariableNames: 'bars'!

----- Method: ServiceGui class>>bars (in category 'registering button bars') -----
bars
	^ bars!

----- Method: ServiceGui class>>browser:classCategoryMenu: (in category 'hooks') -----
browser: b classCategoryMenu: aMenu 
	^ (self new  for:b  id:#browserClassCategoryMenu)  inlineInMenu:aMenu!

----- Method: ServiceGui class>>browser:classMenu: (in category 'hooks') -----
browser: b classMenu: aMenu 
	^ (self new  for:b  id:#browserClassMenu)  inlineInMenu:aMenu!

----- Method: ServiceGui class>>browser:codePaneMenu: (in category 'hooks') -----
browser: b codePaneMenu: aMenu 

	^(self new for: b id: #browserCodePaneMenu) inlineInMenu: aMenu!

----- Method: ServiceGui class>>browser:messageCategoryMenu: (in category 'hooks') -----
browser: b messageCategoryMenu: aMenu 
	^ (self new  for:b  id:#browserMethodCategoryMenu)  inlineInMenu:aMenu!

----- Method: ServiceGui class>>browser:messageListMenu: (in category 'hooks') -----
browser: aBrowser messageListMenu: aMenu 
	^ (self new
		for: aBrowser
		id: #browserMethodMenu) inlineInMenu: aMenu!

----- Method: ServiceGui class>>browserButtonRow: (in category 'hooks') -----
browserButtonRow: aBrowser
	^ (self new for: aBrowser id: #browserButtonBar) buildButtonBar !

----- Method: ServiceGui class>>browserButtonRow:inlinedIn: (in category 'hooks') -----
browserButtonRow: aBrowser inlinedIn: row 
	| bar |
	self buttonBarServices 
		ifTrue: [bar := (self new for: aBrowser id: #browserButtonBar) buildButtonBar.
			row addMorphBack: bar].
	^ row!

----- Method: ServiceGui class>>buttonBarServices (in category 'preferences') -----
buttonBarServices
	^ ServicePreferences valueOfPreference: #useServicesInBrowserButtonBar !

----- Method: ServiceGui class>>initialize (in category 'registering button bars') -----
initialize
	bars := OrderedCollection new.
	(TheWorldMenu respondsTo: #registerOpenCommand:)
		ifTrue: [TheWorldMenu unregisterOpenCommand: 'Services Browser'.
			TheWorldMenu registerOpenCommand: {'Services Browser'. {PreferenceBrowser. #openForServices}}]!

----- Method: ServiceGui class>>inlineServices (in category 'preferences') -----
inlineServices
	^ ServicePreferences valueOfPreference: #inlineServicesInMenu !

----- Method: ServiceGui class>>onlyServices (in category 'preferences') -----
onlyServices
	^ ServicePreferences valueOfPreference: #useOnlyServicesInMenu!

----- Method: ServiceGui class>>openMenuFor: (in category 'opening menus') -----
openMenuFor: aServiceCategory
	(self new menuFor: aServiceCategory) invokeModal!

----- Method: ServiceGui class>>registerBar:for: (in category 'registering button bars') -----
registerBar: aBar for: service
	
	self bars removeAllSuchThat: [:a | a value isNil].
	self bars add: (WeakValueAssociation key: service value: aBar).!

----- Method: ServiceGui class>>updateBar: (in category 'registering button bars') -----
updateBar: cat
	
	self bars 
		select: [:assoc | (assoc key id = cat id) & assoc value notNil] 
		thenDo: [:assoc | | newBar | 
			cat requestor: assoc key requestor.
			newBar := self new buttonBarFor: cat.
			assoc value removeAllMorphs.
			newBar submorphsDo: [:m | assoc value addMorphBack: m]]!

----- Method: ServiceGui class>>updateBars (in category 'registering button bars') -----
updateBars
	
	self bars do: [:assoc | | oldCat cat newBar bar | 
		(bar := assoc value) ifNotNil: [
			oldCat := assoc key.
			cat := oldCat id service.
			cat requestor: oldCat requestor.
			newBar := self new buttonBarFor: cat.
			bar removeAllMorphs.
			newBar submorphsDo: [:m | bar addMorphBack: m]].
		]!

----- Method: ServiceGui class>>updateMenu:forModel:selector: (in category 'hooks') -----
updateMenu: aMenu forModel: aModel selector: selector
	('codePane*' match: selector) ifTrue: [
	(self new for: aModel id: #codeSelectionRefactorings) inlineInMenu: aMenu].
	^ aMenu
	!

----- Method: ServiceGui class>>worldMenu: (in category 'hooks') -----
worldMenu: aMenu
	^ (self new for: aMenu id: #world) inlineInMenu: aMenu!

----- Method: ServiceGui>>bar (in category 'accessing') -----
bar
	^ bar!

----- Method: ServiceGui>>buildButtonBar (in category 'building') -----
buildButtonBar
	bar := self buttonBarFor: service.
	self class registerBar: bar for: service.
	^ bar!

----- Method: ServiceGui>>buttonBarFor: (in category 'servicecategory') -----
buttonBarFor: aServiceCategory 
	self styleBar: self bar.
	aServiceCategory enabledServices
		do: [:each | self bar
				addMorphBack: (self buttonFor: each)].
	^ self bar!

----- Method: ServiceGui>>buttonFor: (in category 'services') -----
buttonFor: aService
	^ aService isCategory ifTrue: [self buttonForCategory: aService]
							ifFalse: [self buttonForAction: aService]!

----- Method: ServiceGui>>buttonForAction: (in category 'serviceactions') -----
buttonForAction: aService 
	"see getstate for availability?"

	| aButton |
	aButton := PluggableButtonMorph 
				on: aService
				getState: nil
				action: #execute.
	self styleButton: aButton.
	aButton
		label: aService buttonLabel;
		setBalloonText: aService description.
	^aButton!

----- Method: ServiceGui>>buttonForCategory: (in category 'servicecategory') -----
buttonForCategory: aService 
	"see getstate for availability?"

	| aButton |
	aButton := PluggableButtonMorph 
				on: [:button | aService requestor: button requestor. 
								self class openMenuFor: aService] 
				getState: nil
				action: #value:.
	aButton arguments: (Array with: aButton).
	self styleButton: aButton.
	aButton
		label: aService buttonLabel.
	^aButton!

----- Method: ServiceGui>>for:id: (in category 'initialization') -----
for: caller id: id 
	service := id service.
	caller ifNotNil: [service requestor: caller requestor]!

----- Method: ServiceGui>>initialize (in category 'initialization') -----
initialize
	super initialize.
	menu := OrderedCollection new.
	bar := AlignmentMorph newRow.
	n := OrderedCollection with: 0!

----- Method: ServiceGui>>inlineInMenu: (in category 'building') -----
inlineInMenu: aMenu 
	^ self class inlineServices
		ifTrue: [self inlineInMenu: aMenu for: service]
		ifFalse: [aMenu]!

----- Method: ServiceGui>>inlineInMenu:for: (in category 'servicecategory') -----
inlineInMenu: aMenu for: aServiceCategory 
	menu addLast: aMenu.
	aServiceCategory enabledServices
		do: [:each | self menuItemFor: each].
	^ self popMenu!

----- Method: ServiceGui>>menu (in category 'accessing') -----
menu
	^ menu last!

----- Method: ServiceGui>>menuFor: (in category 'servicecategory') -----
menuFor: aServiceCategory 
	| submenu |
	submenu := self subMenuFor: aServiceCategory.
	^ submenu
		addTitle: (aServiceCategory menuLabel)!

----- Method: ServiceGui>>menuItemFor: (in category 'services') -----
menuItemFor: aService
	[aService isCategory ifTrue: [self menuItemForCategory: aService]
							ifFalse: [self menuItemForAction: aService]] 
		on: Error
		do: [:er | (self confirm: 'menuItemFor: error. debug?') ifTrue: [er signal]]!

----- Method: ServiceGui>>menuItemForAction: (in category 'serviceactions') -----
menuItemForAction: aServiceAction 
	"Returns a menuItem triggering self"
	self menu
		add: (aServiceAction menuLabelNumbered: self n)
		target: aServiceAction
		selector: #execute.
	self menu lastItem isEnabled: aServiceAction executeCondition.
	self menu balloonTextForLastItem: aServiceAction description!

----- Method: ServiceGui>>menuItemForCategory: (in category 'servicecategory') -----
menuItemForCategory: aServiceCategory 
	"Returns a menuItem triggering self"
	| submenu |
	submenu := self subMenuFor: aServiceCategory.
	self menu add: (aServiceCategory menuLabelNumbered: self n) subMenu: submenu!

----- Method: ServiceGui>>n (in category 'servicecategory') -----
n
	^ n last!

----- Method: ServiceGui>>n: (in category 'servicecategory') -----
n: nn
	n removeLast.
	n addLast: nn!

----- Method: ServiceGui>>popMenu (in category 'servicecategory') -----
popMenu
	| aMenu |
	aMenu := menu removeLast.
	n removeLast.
	self styleMenu: aMenu.
	^ aMenu!

----- Method: ServiceGui>>pushMenu (in category 'servicecategory') -----
pushMenu
	menu addLast: MenuMorph new.
	n addLast: 0!

----- Method: ServiceGui>>styleBar: (in category 'styling') -----
styleBar: aBar
	aBar setNameTo: 'button bar'.
	aBar beSticky;
		 hResizing: #spaceFill;
		wrapCentering: #center;
		cellPositioning: #leftCenter;
		clipSubmorphs: true;
		cellInset: 0;
		color: Preferences defaultWindowColor.!

----- Method: ServiceGui>>styleButton: (in category 'styling') -----
styleButton: aButton 
	aButton color: Color transparent;
	onColor: Color transparent offColor: Color transparent;
	
	borderStyle: (BorderStyle width: 1 color: Color gray);
	askBeforeChanging: true;
	clipSubmorphs: true;
	hResizing: #spaceFill;
	vResizing: #spaceFill.
	^ self!

----- Method: ServiceGui>>styleMenu: (in category 'styling') -----
styleMenu: aMenu 
	"gradient, etc ..?"
	"aMenu color: Color white;
		
		borderStyle: (BorderStyle width: 1 color: Color gray);
		 clipSubmorphs: true;
		 addDropShadow;
		
		shadowColor: (TranslucentColor
				r: 0.0
				g: 0.0
				b: 0.0
				alpha: 0.666);
		 shadowOffset: 1 @ 1"!

----- Method: ServiceGui>>subMenuFor: (in category 'servicecategory') -----
subMenuFor: aServiceCategory 
	self pushMenu.
	aServiceCategory enabledServices
		ifEmpty: [self menuItemFor: ServiceAction new].
	aServiceCategory enabledServices
		doWithIndex: [:each :i | self n: i. self menuItemFor: each].
	^ self popMenu!

Object subclass: #ServiceProvider
	instanceVariableNames: ''
	classVariableNames: ''
	poolDictionaries: ''
	category: 'Services-Base'!

!ServiceProvider commentStamp: 'rr 7/10/2006 15:08' prior: 0!
A ServiceProvider references services that are relevant to a given application.

Each application that wishes to use the Services framework must subclass a ServiceProvider.
This class must define a 'services' method category.
Each method implemented in this category will be automatically called by the framework.
Each of these method should be a unary message (taking no argument), and return a fully initialised instance of ServiceAction or ServiceCategory. There are three possible patterns:

1)

serviceIdentifierAndMethodName
	^ ServiceAction
		text: 'Menu item text'
		button: 'Button text'
		description: 'Longer text that appears in help balloons'
		action: [:r | "Code block fetching data from the requestor instance, r, that is passed to the block"]
		

2)

serviceIdentifierAndMethodName
	^ ServiceAction
		text: 'Menu item text'
		button: 'Button text'
		description: 'Longer text that appears in help balloons'
		action: [:r | "Code block fetching data from the requestor instance, r, that is passed to the block"]
		condition: [:r | "second block returning true if the service can be used at the time being, false otherwise. Data can still be fetched from the requestor instance"]
		
3)

methodNameAndServiceCategoryId
	^ ServiceCategory 
		text: 'Menu text' 
		button: 'Button  text' 
		description: 'Longer descriptive text appearing in help balloons'
	

The organisation of services into categories, and the services bound to keyboard shortcuts are
specified using the Services Browser (see the comment on the class ServicesPreferences for more details). When editing preferences, they are saved as methods on the ServiceProvider, all defined
in the 'saved preferences' method category. Each of thesse methods stores preferences that the provider can replay.
!

ServiceProvider subclass: #BrowserProvider
	instanceVariableNames: ''
	classVariableNames: ''
	poolDictionaries: ''
	category: 'Services-Base-Providers'!

!BrowserProvider commentStamp: 'rr 7/10/2006 15:17' prior: 0!
I define the default categories of services dealing with browsing:
- the class category menu (service identifier: browserClassCategoryMenu)
- the class menu (browserClassMenu)
- the method category menu (browserMethodCategoryMenu)
- the browser method menu (browserMethodMenu)
- the browser button bar (browserButtonBar)
- the browser code pane/selection menu (browserCodePaneMenu)!

----- Method: BrowserProvider class>>initialize (in category 'initialize-release') -----
initialize
	ServiceRegistry current buildProvider: self new!

----- Method: BrowserProvider>>browser (in category 'services') -----
browser
	^ ServiceCategory  text: 'Browser' 
					button: 'browser' 
					description: 'The browser menus'!

----- Method: BrowserProvider>>browserButtonBar (in category 'services') -----
browserButtonBar
	^ ServiceCategory 
		 text:'button bar'
		 button:'button'
		 description:'the browser button bar'!

----- Method: BrowserProvider>>browserClassCategoryMenu (in category 'services') -----
browserClassCategoryMenu
	^ ServiceCategory 
		 text:'Class Category'
		 button:'class cat'
		 description:'The browser class category menu'!

----- Method: BrowserProvider>>browserClassMenu (in category 'services') -----
browserClassMenu
	^ ServiceCategory 
		 text:'Class'
		 button:'class'
		 description:'The browser class menu'!

----- Method: BrowserProvider>>browserClassMenushortcut (in category 'saved preferences') -----
browserClassMenushortcut
	^ #(#'Shortcut for browserClassMenu:' '' 1000 )!

----- Method: BrowserProvider>>browserCodePaneMenu (in category 'services') -----
browserCodePaneMenu
	^ ServiceCategory text: 'Code Pane' 
						button: 'pane' 
						description: 'The browser code pane menu'!

----- Method: BrowserProvider>>browserMethodCategoryMenu (in category 'services') -----
browserMethodCategoryMenu
	^ ServiceCategory 
		 text:'Method Category'
		 button:'method cat'
		 description:'The browser method menu'!

----- Method: BrowserProvider>>browserMethodMenu (in category 'services') -----
browserMethodMenu
	^ ServiceCategory 
		 text:'Method'
		 button:'method'
		 description:'The browser method menu'!

----- Method: BrowserProvider>>browserMethodMenushortcut (in category 'saved preferences') -----
browserMethodMenushortcut
	^ #(#'Shortcut for browserMethodMenu:' '' 1000 )!

----- Method: ServiceProvider class>>newProviderFor: (in category 'provider creation') -----
newProviderFor: packageName
	| cls clsName |
	clsName := ((packageName copyWithout: $-) , 'ServiceProvider') asSymbol.
	cls := self subclass: clsName
		instanceVariableNames: '' 
		classVariableNames: '' 
		poolDictionaries: ''
		category: packageName.
	cls class compile: 'initialize 
	ServiceRegistry buildProvider: self new' classified: 'initialization'.
	^ cls!

----- Method: ServiceProvider class>>registeredProviders (in category 'accessing') -----
registeredProviders
	^ self allSubclasses collect: [:each | each new]!

----- Method: ServiceProvider>>performAndSetId: (in category 'accessing') -----
performAndSetId: aSymbol 
	| service |
	service := self perform: aSymbol.
	service id: aSymbol.
	^service!

----- Method: ServiceProvider>>registeredServices (in category 'accessing') -----
registeredServices
	
	^ self services collect: [:each | self performAndSetId: each]!

----- Method: ServiceProvider>>replayPreferences (in category 'persistence') -----
replayPreferences
	ServicePreferences replayPreferences: self savedPreferences!

----- Method: ServiceProvider>>savePreferencesFor: (in category 'persistence') -----
savePreferencesFor: aService 
	
	"pref := ServicePreferences preferenceAt: aService shortcutPreference.
	strm := WriteStream with: ''.
	strm nextPutAll: aService id;
		 nextPutAll: 'shortcut';
		 cr;
		 tab;
		 nextPutAll: '^ ';
		 nextPutAll: {pref name. pref preferenceValue. 1000} storeString.
	self class compileSilently: strm contents classified: 'saved preferences'."
	aService isCategory
		ifTrue: [aService externalPreferences
				doWithIndex: [:e :i | | strm | 
					strm := WriteStream with: aService id asString.
					strm nextPutAll: e id asString;
						 cr;
						 tab;
						 nextPutAll: '^ ';
						 nextPutAll: {aService childrenPreferences. e id. i} storeString.
					e provider class compileSilently: strm contents classified: 'saved preferences']]!

----- Method: ServiceProvider>>savedPreferences (in category 'persistence') -----
savedPreferences
	 ^ (self class organization listAtCategoryNamed: #'saved preferences')
			collect: [:e | self perform: e]!

----- Method: ServiceProvider>>services (in category 'accessing') -----
services
	^ self class organization listAtCategoryNamed: #services!

ServiceProvider subclass: #WorldMenuProvider
	instanceVariableNames: ''
	classVariableNames: ''
	poolDictionaries: ''
	category: 'Services-Base-Providers'!

!WorldMenuProvider commentStamp: 'rr 7/10/2006 15:19' prior: 0!
I define services and categories:
- The world menu category (identifier:  world), where services and categories can be put to be displayed in the world menu.
- The preferencesMenu category, where services about services and preferences can be put
- th open menu!

----- Method: WorldMenuProvider class>>initialize (in category 'initialize-release') -----
initialize
	ServiceRegistry current buildProvider: self new!

----- Method: WorldMenuProvider>>browserMethodMenucreateNewService (in category 'saved preferences') -----
browserMethodMenucreateNewService
	^ #(#'Items in browserMethodMenu:' #createNewService 1 )!

----- Method: WorldMenuProvider>>closeTopWindow (in category 'services') -----
closeTopWindow
	^ ServiceAction
		text: 'Close top window'
		button: 'close window'
		description: 'Closes the focused window'
		action: [:r | SystemWindow topWindow delete]!

----- Method: WorldMenuProvider>>convertOpenCommand: (in category 'service registering') -----
convertOpenCommand: array 
	| description |
	description := array size > 2 
				ifTrue: [array third]
				ifFalse: ['none available'].
	^ServiceAction 
		id: array first asSymbol
		text: array first
		button: array first
		description: description
		action: [array second first perform: array second second]!

----- Method: WorldMenuProvider>>createNewService (in category 'services') -----
createNewService
	^ ServiceAction 
		text: 'Create new service' 
		button: 'new service' 
		description: 'Define a new service provided by this package' 
		action: [:r | | s p |
			s := r caption: 'enter service identifier'; getSymbol.
			p := r getPackageProvider.
			p compile: s, ' 
	^ ServiceAction 
		"Open the service browser to set the menu position and the keyboard shortcut"
		text: ''fill menu label''
		button: ''short button text''
		description: ''longer text for balloon help''
		action: [:r | "action block"]
		condition: [:r | "optional condition block"]' classified: 'services'.
			r getBrowser browseReference: (MethodReference class: p selector: s)]!

----- Method: WorldMenuProvider>>helpOnServices (in category 'services') -----
helpOnServices
	^ ServiceAction
		text: 'Help on Services'
		button: 'services help'
		description: 'Introductory text about services'
		action: [StringHolder new contents: self servicesHelpText; openLabel: 'Introduction to Services'].!

----- Method: WorldMenuProvider>>nextWindow (in category 'services') -----
nextWindow
	^ ServiceAction text: 'Switch to next window' button: 'next window' description: 'Switches to the next window' action: [:r | SystemWindow sendTopWindowToBack]!

----- Method: WorldMenuProvider>>openMenu (in category 'services') -----
openMenu
	^ ServiceCategory text: 'Open' button: 'open' description: 'The open menu'!

----- Method: WorldMenuProvider>>preferencesBrowser (in category 'services') -----
preferencesBrowser
	^ ServiceAction text: 'Preference Browser' button: 'pref. browser' description: 'Open the preference browser to edit various Squeak settings' action: [PreferenceBrowser open].!

----- Method: WorldMenuProvider>>preferencesMenu (in category 'services') -----
preferencesMenu
	^ ServiceCategory text: 'Preferences & Services' button: 'preferences' description: 'Menu related to editing preferences'!

----- Method: WorldMenuProvider>>preferencesMenuhelpOnServices (in category 'saved preferences') -----
preferencesMenuhelpOnServices
	^ #(#'Items in preferencesMenu:' #helpOnServices 3 )!

----- Method: WorldMenuProvider>>preferencesMenupreferencesBrowser (in category 'saved preferences') -----
preferencesMenupreferencesBrowser
	^ #(#'Items in preferencesMenu:' #preferencesBrowser 1 )!

----- Method: WorldMenuProvider>>preferencesMenurebuildRegistry (in category 'saved preferences') -----
preferencesMenurebuildRegistry
	^ #(#'Items in preferencesMenu:' #rebuildRegistry 4 )!

----- Method: WorldMenuProvider>>preferencesMenuservicesBrowser (in category 'saved preferences') -----
preferencesMenuservicesBrowser
	^ #(#'Items in preferencesMenu:' #servicesBrowser 2 )!

----- Method: WorldMenuProvider>>preferencesMenushortcut (in category 'saved preferences') -----
preferencesMenushortcut
	^ #(#'Shortcut for preferencesMenu:' '' 1000 )!

----- Method: WorldMenuProvider>>rebuildRegistry (in category 'services') -----
rebuildRegistry
	^ ServiceAction text: 'Rebuild service registry' button: 'rebuild registry' description: 'Rebuilds the service registry to scan for newly defined services' action: [ServiceRegistry rebuild].!

----- Method: WorldMenuProvider>>servicesBrowser (in category 'services') -----
servicesBrowser
	^ ServiceAction text: 'Services Browser' button: 'services' description: 'Open a preference browser to edit several Squeak menus' action: [PreferenceBrowser openForServices].!

----- Method: WorldMenuProvider>>servicesHelpText (in category 'accessing') -----
servicesHelpText
	^ '
	This is an overview of the main concepts of the services framework. More details are available in class comments. The aim is to help you defining services step by step. The three main classes are: 

-ServiceAction
-ServiceCategory
-ServiceProvider

Alongside them, a tool to use is the Services Browser. It can be found in the world menu, under the ''Preferences & Services'' menu heading (in which you found this text).
	
	ServiceAction are executable objects in various contexts.
They can be displayed as buttons or menu items or bounded to keyboard shortcuts.

	ServiceCategory are categories of services. They are also services, so a ServiceCategory can be included in another, forming a tree of Services. ServiceCategories can be displayed with menus, or button bars.
	
	A ServiceProvider references services that are relevant to a given application.
Each application that wishes to use the Services framework must subclass a ServiceProvider.
This class must define a ''services'' method category.
Each method implemented in this category will be automatically called by the framework.
Each of these method should be a unary message (taking no argument), and return a fully initialised instance of ServiceAction or ServiceCategory. There are three possible patterns:

1)
serviceIdentifierAndMethodName
	^ ServiceAction
		text: ''Menu item text''
		button:''Button text''
		description: ''Longer text that appears in help balloons''
		action: [:r | "Code block fetching data from the requestor instance, r, that is passed to the block"]
		
2)
serviceIdentifierAndMethodName
	^ ServiceAction
		text: ''Menu item text''
		button: ''Button text''
		description: ''Longer text that appears in help balloons''
		action: [:r | "Code block fetching data from the requestor instance, r, that is passed to the block"]
		condition: [:r | "second block returning true if the service can be used at the time being, false otherwise. Data can still be fetched from the requestor instance"]
		
3)
methodNameAndServiceCategoryId
	^ ServiceCategory 
		text: ''Menu text''
		button: ''Button  text'' 
		description: ''Longer descriptive text appearing in help balloons''

The block given to the ServiceActions can take an instance of the Requestor class as parameter. You can fetch data from these. The generic format is to call methods starting with ''get'' on the requestor, like getClass, getMessageName for services related to the browser.	

The organisation of services into categories, and the services bound to keyboard shortcuts are
specified using the Services Browser, based on the Preference Browser by Hernan Tylim. When editing preferences, they are saved as methods on the ServiceProvider, all defined in the ''saved preferences'' method category. 

When opening the Services Browser you see a list of preference categories on the left, and the preferences inside this category on the right. The main preference categories for services are: 

-- keyboard shortcuts -- : several text preferences, one per keyboard shortcuts. To edit them,  enter a service identifier (equal to the method name under which it is defined in its ServiceProvider), and accept with alt-s or enter

-- menu contents -- : All the service categories in the image have a text preference under here. To edit it, enter the services identifiers you wish to put in this category, separating them with a single space character. The order is important: it defines the order of the items in menus.

-- settings -- : general boolean preferences.

Then there is a preference category for each provider in the image. Under each, you will find:
A boolean preference for each service in the image. If it is false, the service will not appear in menus. 
The text preference for each service category defined by the service provider. This is the same as the one appearing in the menu contents preference category.

Some identifiers of categories already appearing in the UI are:
- world : the world menu
- preferencesMenu
- browserClasssCategoryMenu
- browserClassMenu
- browserMethodCategoryMenu
- browserMethodMenu
- browserCodePaneMenu
- browserButtonBar

After editing these preferences to match the services and categories you defined for your application, you should be done.

	Romain Robbes'!

----- Method: WorldMenuProvider>>world (in category 'services') -----
world
	^ ServiceCategory text: 'World' button: 'world' description: 'The world menu'!

----- Method: WorldMenuProvider>>worldpreferencesMenu (in category 'saved preferences') -----
worldpreferencesMenu
	^ #(#'Items in world:' #preferencesMenu 1 )!

----- Method: WorldMenuProvider>>worldshortcut (in category 'saved preferences') -----
worldshortcut
	^ #(#'Shortcut for world:' '' 1000 )!

Object subclass: #ServiceRegistry
	instanceVariableNames: 'services interactive'
	classVariableNames: 'Current'
	poolDictionaries: ''
	category: 'Services-Base'!

!ServiceRegistry commentStamp: 'rr 7/10/2006 15:10' prior: 0!
The ServiceRegistry is the repository in which services are stored. They are stored in
a dictionary, and keyed by their identifier (which is the name of the method they were defined in).

The registry handles the intialization, building and referencing processes as well.!

----- Method: ServiceRegistry class>>current (in category 'as yet unclassified') -----
current
	^ Current ifNil: [Current := self new]!

----- Method: ServiceRegistry class>>ifInteractiveDo: (in category 'as yet unclassified') -----
ifInteractiveDo: aBlock
	self current isInteractive ifTrue: [aBlock value]!

----- Method: ServiceRegistry class>>initialize (in category 'as yet unclassified') -----
initialize

	self rebuild.
	SystemChangeNotifier uniqueInstance
		notify: self
		ofSystemChangesOfItem: #method 
		using: #methodChanged:
		!

----- Method: ServiceRegistry class>>methodChanged: (in category 'as yet unclassified') -----
methodChanged: event
	self ifInteractiveDo: [
	| cls | 
	cls := event itemClass. 
	((event changeKind = #removed) not & (cls inheritsFrom: ServiceProvider) and: [cls new services includes: event itemSelector])
		ifTrue: [[self current addService: (cls new performAndSetId: event itemSelector)
					provider: cls]
			on: Error do: [self inform: 'Service format seems to be incorrect']]]!

----- Method: ServiceRegistry class>>rebuild (in category 'as yet unclassified') -----
rebuild
	| old |
	old := Current.
	[Current := self new.
	Current build]
		on: Error
		do: [:err | (self confirm: 'An error occured during build. 
								Debug it?')
				ifTrue: [err signal].
				Current := old]!

----- Method: ServiceRegistry>>addService:provider: (in category 'building') -----
addService: aService provider: p
	services  at:aService id  put:aService.
	aService provider: p.
	aService insertPreferences
!

----- Method: ServiceRegistry>>beNotInteractiveDuring: (in category 'building') -----
beNotInteractiveDuring: aBlock
	interactive := false.
	aBlock value.
	interactive := true!

----- Method: ServiceRegistry>>build (in category 'building') -----
build
	"ServicePreferences wipe."
	self
		beNotInteractiveDuring: [
			| pr |
			ServiceProvider registeredProviders
				do: [:p | p registeredServices
						do: [:each | self addService: each provider: p class]].
			pr := ServiceProvider registeredProviders
						gather: [:p | p savedPreferences].
			ServicePreferences replayPreferences: pr.
			].
	ServiceGui updateBars.
	ServiceShortcuts setPreferences!

----- Method: ServiceRegistry>>buildProvider: (in category 'building') -----
buildProvider: p
	self beNotInteractiveDuring: [
		p registeredServices do: [:each | self addService: each provider: p class].
		p replayPreferences]
	!

----- Method: ServiceRegistry>>categories (in category 'accessing') -----
categories
	^ self serviceCollection select: [:s | s isCategory]!

----- Method: ServiceRegistry>>initialize (in category 'initialize-release') -----
initialize
	services := Dictionary new.
	interactive := true!

----- Method: ServiceRegistry>>isInteractive (in category 'accessing') -----
isInteractive
	^ interactive!

----- Method: ServiceRegistry>>serviceCollection (in category 'accessing') -----
serviceCollection
	^ services asArray!

----- Method: ServiceRegistry>>serviceWithId: (in category 'accessing') -----
serviceWithId: aSymbol
	^ services at: aSymbol 
				ifAbsent: [nil]!

----- Method: ServiceRegistry>>services (in category 'accessing') -----
services
	^ self serviceCollection reject: [:s | s isCategory]!

Object subclass: #ServiceShortcuts
	instanceVariableNames: ''
	classVariableNames: ''
	poolDictionaries: ''
	category: 'Services-Base'!
ServiceShortcuts class
	instanceVariableNames: 'map'!

!ServiceShortcuts commentStamp: 'rr 7/10/2006 15:14' prior: 0!
A data structures implementing a simple form of keyboard shortucts is defined on the class side.

Available keyboard shortcuts are: 

command-0 to command-9 (command is also called alt on some systems).
control-0 to control-0
command-control-0 to command-control-9 (command is also alt)
control-command-left arrow
control-command-up arrow
control-command-right arrow
control-command-down arrow

Using the Services Browser (see class ServicePreferences), these shortcuts can be bound to service identifiers.!
ServiceShortcuts class
	instanceVariableNames: 'map'!

----- Method: ServiceShortcuts class>>arrowShortcut:event: (in category 'as yet unclassified') -----
arrowShortcut: str event: event 
	| key s |
	key := event keyCharacter caseOf: {
				[Character arrowDown] -> ['down'].
				[Character arrowUp] -> ['up'].
				[Character arrowLeft] -> ['left'].
				[Character arrowRight] -> ['right']}.
	s := self map
				at: str , key
				ifAbsent: [^ self].
	s serviceOrNil
		ifNotNil: [:sv | sv execute.
	event wasHandled: true]!

----- Method: ServiceShortcuts class>>changeShortcut:to: (in category 'as yet unclassified') -----
changeShortcut: shortcut to: aString
	aString isBlock ifTrue: [^self map at: shortcut put: aString].
	(aString beginsWith: '[') ifTrue: [^self map at: shortcut put: aString].
	aString isEmpty ifTrue: [self map removeKey: shortcut ifAbsent: []]
				ifFalse: [self map at: shortcut put: aString]!

----- Method: ServiceShortcuts class>>handleKeystroke: (in category 'as yet unclassified') -----
handleKeystroke: event 
	[event isKeystroke
		ifTrue: [self process: event]]
		on: Error
		do: [:e | (self confirm: 'shortcut error. debug?') ifTrue: [e signal]]!

----- Method: ServiceShortcuts class>>insertPrefShortcut: (in category 'as yet unclassified') -----
insertPrefShortcut: short
					ServicePreferences
						addPreference: short
						categories: #('-- keyboard shortcuts --' )
						default: ''
						balloonHelp: 'enter a service id to bind it to this shortcut'
						projectLocal: false
						changeInformee: [self
								changeShortcut: short
								to: (ServicePreferences valueOfPreference: short)]
						changeSelector: #value
						type: #String!

----- Method: ServiceShortcuts class>>map (in category 'as yet unclassified') -----
map
	^ map ifNil: [map := Dictionary new]!

----- Method: ServiceShortcuts class>>process: (in category 'as yet unclassified') -----
process: event 
	event keyCharacter isDigit
		ifTrue: [event commandKeyPressed & event controlKeyPressed
				ifTrue: [^ self shortcut: 'ctrl-cmd-' event: event].
			event commandKeyPressed
				ifTrue: [^ self shortcut: 'cmd-' event: event].
			event controlKeyPressed
				ifTrue: [^ self shortcut: 'ctrl-' event: event]].
	({Character arrowUp. Character arrowDown. Character arrowLeft. Character arrowRight} includes: event keyCharacter)
		ifTrue: [event commandKeyPressed & event controlKeyPressed
				ifTrue: [^ self arrowShortcut: 'ctrl-cmd-' event: event].
			]!

----- Method: ServiceShortcuts class>>setPreferences (in category 'as yet unclassified') -----
setPreferences
	| mm |
	mm := self map copy.
	(0 to: 9)
		do: [:i | #('ctrl-' 'cmd-' 'ctrl-cmd-' )
				do: [:str | 
					| short | 
					short := (str , i asString) asSymbol.
					self insertPrefShortcut: short]].
	#(#up #down #left #right )
		do: [:s | 
			self insertPrefShortcut: ('ctrl-cmd-' , s) asSymbol.].
	mm
		keysAndValuesDo: [:k :v | ServicePreferences setPreference: k toValue: v].
	((Array new: 3) at: 1 put: ((Array new: 3) at: 1 put: #inlineServicesInMenu;
			 at: 2 put: true;
			 at: 3 put: 'Inline services within squeak menus';
			 yourself);
		 at: 2 put: ((Array new: 3) at: 1 put: #useOnlyServicesInMenu;
			 at: 2 put: false;
			 at: 3 put: 'Use only services and not regular menu items';
			 yourself);
		 at: 3 put: ((Array new: 3) at: 1 put: #useServicesInBrowserButtonBar;
			 at: 2 put: true;
			 at: 3 put: 'Use a service-based button bar';
			 yourself);
		 yourself)
		do: [:tr | ServicePreferences
				addPreference: tr first
				categories: #('-- settings --' )
				default: tr second
				balloonHelp: tr third]!

----- Method: ServiceShortcuts class>>shortcut:event: (in category 'as yet unclassified') -----
shortcut: str event: event 
	| s |
	Transcript cr.
	s := self map
				at: str , event keyCharacter asString
				ifAbsent: [^ self].
	(s beginsWith: '[') ifTrue: [^ (Compiler evaluateUnloggedForSelf:  s) value].
	s serviceOrNil
		ifNotNil: [:sv | sv execute.
	event wasHandled: true]!

----- Method: FillInTheBlankMorph>>selection (in category '*services-base') -----
selection
	"answers what is actually selected in the morph"
	^ textPane selectionInterval!

----- Method: BlockClosure>>valueWithRequestor: (in category '*services-base') -----
valueWithRequestor: aRequestor 
	"To do later: make the fillInTheBlank display more informative captions.
	Include the description of the service, and maybe record steps"

	^ self numArgs isZero 
		ifTrue: [self value]
		ifFalse: [self value: aRequestor]!

----- Method: SequenceableCollection>>startsWith: (in category '*services-base') -----
startsWith: start

	| comp |
	self deprecated: 'Use #beginsWith:'.
	self size < start size ifTrue: [^ false].
	comp := true.
	(self first: start size) with: start
		do: [:ea :ea2 | ea = ea2 ifFalse: [comp := false]].
	^ comp!

----- Method: Morph>>requestor (in category '*services-base') -----
requestor
	^ owner ifNil: [super requestor] ifNotNil: [owner requestor]!

Warning subclass: #ServiceCancelled
	instanceVariableNames: ''
	classVariableNames: ''
	poolDictionaries: ''
	category: 'Services-Base'!

!ServiceCancelled commentStamp: 'rr 4/1/2004 18:24' prior: 0!
Exception raised when a service is cancelled, to inform the user.!

----- Method: ServiceCancelled>>defaultAction (in category 'handling') -----
defaultAction
	Transcript cr; show: 'service has been cancelled'!

----- Method: ServiceCancelled>>messageText (in category 'accessing') -----
messageText
	^ 'Service has been cancelled'!

----- Method: CodeHolder>>requestor (in category '*services-base') -----
requestor
	^ (BrowserRequestor new) browser: self; yourself!

----- Method: Browser>>browseReference: (in category '*services-base') -----
browseReference: ref
	self okToChange ifTrue: [
	self selectCategoryForClass: ref actualClass theNonMetaClass.
	self selectClass: ref actualClass theNonMetaClass .
	ref actualClass isMeta ifTrue: [self indicateClassMessages].
	self changed: #classSelectionChanged.
	self selectMessageCategoryNamed: ref category.
	self selectedMessageName: ref methodSymbol.
	]!

----- Method: Browser>>menuHook:named:shifted: (in category '*services-base') -----
menuHook: aMenu named: aSymbol shifted: aBool
	"Enhance aMenu with registered services."
	aSymbol 
		caseOf: 
			{ [ #classListMenu ] 		-> [ ServiceGui browser: self classMenu: aMenu ].
			[ #codePaneMenu ]			-> [ ServiceGui browser: self codePaneMenu: aMenu ].
			[ #messageCategoryMenu]	-> [ ServiceGui browser: self messageCategoryMenu: aMenu ].
			[ #messageListMenu ] 		-> [ ServiceGui browser: self messageListMenu: aMenu ].
			[ #systemCategoryMenu ] 	-> [ ServiceGui browser: self classCategoryMenu: aMenu ] } 
		otherwise: [ "do nothing" ]!

----- Method: Browser>>methodReference (in category '*services-base') -----
methodReference 
	| cls sel |
	cls := self selectedClassOrMetaClass.
	sel := self selectedMessageName.
	cls isNil | sel isNil ifTrue: [^nil].
	^ MethodReference class: cls selector: sel!

----- Method: Browser>>optionalButtonRow (in category '*services-base') -----
optionalButtonRow
	^ServiceGui browserButtonRow: self inlinedIn: super optionalButtonRow!

----- Method: Browser>>selectReference: (in category '*services-base') -----
selectReference: ref
	self browseReference: ref!



More information about the Squeak-dev mailing list