Chris Muller uploaded a new version of ToolBuilder-Kernel to project Squeak 4.6:
http://source.squeak.org/squeak46/ToolBuilder-Kernel-mt.89.mcz
==================== Summary ====================
Name: ToolBuilder-Kernel-mt.89
Author: mt
Time: 12 May 2015, 9:02:56.628 pm
UUID: 5175a13e-cae3-8f48-bd99-8a7d0d012866
Ancestors: ToolBuilder-Kernel-mt.88
Allow input fields to provide soft-line-wrap.
==================== Snapshot ====================
SystemOrganization addCategory: #'ToolBuilder-Kernel'!
Notification subclass: #ProgressInitiationException
instanceVariableNames: 'workBlock maxVal minVal aPoint progressTitle'
classVariableNames: 'PreferredProgressBarPosition'
poolDictionaries: ''
category: 'ToolBuilder-Kernel'!
!ProgressInitiationException commentStamp: '<historical>' prior: 0!
I provide a way to alter the behavior of the old-style progress notifier in String. See examples in:
ProgressInitiationException testWithout.
ProgressInitiationException testWith.
!
----- Method: ProgressInitiationException class>>display:at:from:to:during: (in category 'signalling') -----
display: aString at: aPoint from: minVal to: maxVal during: workBlock
^ self new
display: aString
at: (aPoint ifNil: [ self preferredProgressBarPoint ])
from: minVal
to: maxVal
during: workBlock!
----- Method: ProgressInitiationException class>>display:from:to:during: (in category 'signalling') -----
display: aString from: minVal to: maxVal during: workBlock
^ self
display: aString
at: nil
from: minVal
to: maxVal
during: workBlock!
----- Method: ProgressInitiationException class>>preferredProgressBarPoint (in category 'accessing') -----
preferredProgressBarPoint
^ self preferredProgressBarPosition = #cursorPoint
ifTrue: [ Sensor cursorPoint ]
ifFalse: [ UIManager default screenBounds perform: self preferredProgressBarPosition ]!
----- Method: ProgressInitiationException class>>preferredProgressBarPosition (in category 'accessing') -----
preferredProgressBarPosition
^ PreferredProgressBarPosition ifNil: [ #center ]!
----- Method: ProgressInitiationException class>>preferredProgressBarPosition: (in category 'accessing') -----
preferredProgressBarPosition: aSymbol
"Specify any of: #center, #topCenter, #bottomCenter, #leftCenter, #rightCenter, #topLeft, #topRight, #bottomLeft or #bottomRight or #cursorPoint."
^ PreferredProgressBarPosition!
----- Method: ProgressInitiationException class>>testInnermost (in category 'examples and tests') -----
testInnermost
"test the progress code WITHOUT special handling"
^'Now here''s some Real Progress'
displayProgressFrom: 0
to: 10
during: [ :bar |
1 to: 10 do: [ :x |
bar value: x. (Delay forMilliseconds: 500) wait.
x = 5 ifTrue: [1/0]. "just to make life interesting"
].
'done'
].
!
----- Method: ProgressInitiationException class>>testWith (in category 'examples and tests') -----
testWith
"test progress code WITH special handling of progress notifications"
^[ self testWithAdditionalInfo ]
on: ProgressInitiationException
do: [ :ex |
ex sendNotificationsTo: [ :min :max :curr |
Transcript show: min printString,' ',max printString,' ',curr printString; cr
].
].
!
----- Method: ProgressInitiationException class>>testWithAdditionalInfo (in category 'examples and tests') -----
testWithAdditionalInfo
^{'starting'. self testWithout. 'really!!'}!
----- Method: ProgressInitiationException class>>testWithout (in category 'examples and tests') -----
testWithout
"test the progress code WITHOUT special handling"
^[self testInnermost]
on: ZeroDivide
do: [ :ex | ex resume]
!
----- Method: ProgressInitiationException>>defaultAction (in category 'handling') -----
defaultAction
self resume!
----- Method: ProgressInitiationException>>defaultResumeValue (in category 'handling') -----
defaultResumeValue
^ UIManager default
displayProgress: progressTitle
at: aPoint
from: minVal
to: maxVal
during: workBlock!
----- Method: ProgressInitiationException>>display:at:from:to:during: (in category 'initialize-release') -----
display: argString at: argPoint from: argMinVal to: argMaxVal during: argWorkBlock
progressTitle := argString.
aPoint := argPoint.
minVal := argMinVal.
maxVal := argMaxVal.
workBlock := argWorkBlock.
^self signal!
----- Method: ProgressInitiationException>>sendNotificationsTo: (in category 'initialize-release') -----
sendNotificationsTo: aNewBlock
self resume: (
workBlock value: [ :barVal |
aNewBlock value: minVal value: maxVal value: barVal
]
)
!
----- Method: String>>displayProgressAt:from:to:during: (in category '*toolbuilder-kernel') -----
displayProgressAt: aPoint from: minVal to: maxVal during: workBlock
"Display this string as a caption over a progress bar while workBlock is evaluated.
EXAMPLE (Select next 6 lines and Do It)
'Now here''s some Real Progress'
displayProgressAt: Sensor cursorPoint
from: 0 to: 10
during: [:bar |
1 to: 10 do: [:x | bar value: x.
(Delay forMilliseconds: 500) wait]].
HOW IT WORKS (Try this in any other language :-)
Since your code (the last 2 lines in the above example) is in a block,
this method gets control to display its heading before, and clean up
the screen after, its execution.
The key, though, is that the block is supplied with an argument,
named 'bar' in the example, which will update the bar image every
it is sent the message value: x, where x is in the from:to: range.
"
^ProgressInitiationException
display: self
at: aPoint
from: minVal
to: maxVal
during: workBlock!
----- Method: String>>displayProgressFrom:to:during: (in category '*toolbuilder-kernel') -----
displayProgressFrom: minVal to: maxVal during: workBlock
"Display this string as a caption over a progress bar while workBlock is evaluated.
EXAMPLE (Select next 6 lines and Do It)
'Now here''s some Real Progress'
displayProgressFrom: 0 to: 10
during: [:bar |
1 to: 10 do: [:x | bar value: x.
(Delay forMilliseconds: 500) wait]]."
^ self
displayProgressAt: nil
from: minVal
to: maxVal
during: workBlock!
----- Method: Object>>confirm: (in category '*ToolBuilder-Kernel-error handling') -----
confirm: queryString
"Put up a yes/no menu with caption queryString. Answer true if the
response is yes, false if no. This is a modal question--the user must
respond yes or no."
"nil confirm: 'Are you hungry?'"
^ UIManager default confirm: queryString!
----- Method: Object>>confirm:orCancel: (in category '*ToolBuilder-Kernel-error handling') -----
confirm: aString orCancel: cancelBlock
"Put up a yes/no/cancel menu with caption aString. Answer true if
the response is yes, false if no. If cancel is chosen, evaluate
cancelBlock. This is a modal question--the user must respond yes or no."
^ UIManager default confirm: aString orCancel: cancelBlock!
----- Method: Object>>inform: (in category '*ToolBuilder-Kernel-user interface') -----
inform: aString
"Display a message for the user to read and then dismiss. 6/9/96 sw"
aString isEmptyOrNil ifFalse: [UIManager default inform: aString]!
Object subclass: #ToolBuilder
instanceVariableNames: 'parent'
classVariableNames: 'OpenToolsAttachedToMouseCursor'
poolDictionaries: ''
category: 'ToolBuilder-Kernel'!
!ToolBuilder commentStamp: '<historical>' prior: 0!
I am a tool builder, that is an object which knows how to create concrete widgets from abstract specifications. Those specifications are used by tools which want to be able to function in diverse user interface paradigms, such as MVC, Morphic, Tweak, wxWidgets etc.
The following five specs must be supported by all implementations:
* PluggableButton
* PluggableList
* PluggableText
* PluggablePanel
* PluggableWindow
The following specs are optional:
* PluggableTree: If not supported, the tool builder must answer nil when asked for a pluggableTreeSpec. Substitution will require client support so clients must be aware that some tool builders may not support trees (MVC for example, or Seaside). See examples in FileListPlus or TestRunnerPlus.
* PluggableMultiSelectionList: If multi-selection lists are not supported, tool builder will silently support regular single selection lists.
* PluggableInputField: Intended as a HINT for the builder that this widget will be used as a single line input field. Unless explicitly supported it will be automatically substituted by PluggableText.
* PluggableActionButton: Intended as a HINT for the builder that this widget will be used as push (action) button. Unless explicitly supported it will be automatically substituted by PluggableButton.
* PluggableRadioButton: Intended as a HINT for the builder that this widget will be used as radio button. Unless explicitly supported it will be automatically substituted by PluggableButton.
* PluggableCheckBox: Intended as a HINT for the builder that this widget will be used as check box. Unless explicitly supported it will be automatically substituted by PluggableButton.
!
----- Method: ToolBuilder class>>build: (in category 'instance creation') -----
build: aClass
^self default build: aClass!
----- Method: ToolBuilder class>>default (in category 'accessing') -----
default
"Answer the default tool builder"
^ Project current uiManager toolBuilder
!
----- Method: ToolBuilder class>>findDefault (in category 'accessing') -----
findDefault
"Answer a default tool builder"
| builderClass |
"Note: The way the following is phrased ensures that you can always make 'more specific' builders merely by subclassing a tool builder and implementing a more specific way of reacting to #isActiveBuilder. For example, a BobsUIToolBuilder can subclass MorphicToolBuilder and (if enabled, say Preferences useBobsUITools) will be considered before the parent (generic MorphicToolBuilder)."
builderClass := self allSubclasses
detect:[:any| any isActiveBuilder and:[
any subclasses noneSatisfy:[:sub| sub isActiveBuilder]]] ifNone:[nil].
builderClass ifNotNil: [^builderClass ].
^self error: 'ToolBuilder not found'!
----- Method: ToolBuilder class>>isActiveBuilder (in category 'accessing') -----
isActiveBuilder
"Answer whether I am the currently active builder"
^false!
----- Method: ToolBuilder class>>open: (in category 'instance creation') -----
open: aClass
^self default open: aClass!
----- Method: ToolBuilder class>>open:label: (in category 'instance creation') -----
open: aClass label: aString
^self default open: aClass label: aString!
----- Method: ToolBuilder class>>openToolsAttachedToMouseCursor (in category 'preferences') -----
openToolsAttachedToMouseCursor
<preference: 'Open Tools Attached to Mouse Cursor'
categoryList: #(Tools mouse)
description: 'If enabled, new tool windows will be attached to the mouse cursor to be positioned on screen with an additional click. Only, if a mouse event triggered that tool.'
type: #Boolean>
^ OpenToolsAttachedToMouseCursor ifNil: [false]!
----- Method: ToolBuilder class>>openToolsAttachedToMouseCursor: (in category 'preferences') -----
openToolsAttachedToMouseCursor: aBoolean
OpenToolsAttachedToMouseCursor := aBoolean.!
----- Method: ToolBuilder>>build: (in category 'building') -----
build: anObject
"Build the given object using this tool builder"
^anObject buildWith: self!
----- Method: ToolBuilder>>buildAll:in: (in category 'building') -----
buildAll: aList in: newParent
"Build the given set of widgets in the new parent"
| prior |
aList ifNil:[^self].
prior := parent.
parent := newParent.
aList do:[:each| each buildWith: self].
parent := prior.
!
----- Method: ToolBuilder>>buildPluggableActionButton: (in category 'widgets optional') -----
buildPluggableActionButton: spec
^self buildPluggableButton: spec!
----- Method: ToolBuilder>>buildPluggableAlternateMultiSelectionList: (in category 'widgets optional') -----
buildPluggableAlternateMultiSelectionList: aSpec
^ self buildPluggableList: aSpec!
----- Method: ToolBuilder>>buildPluggableButton: (in category 'widgets required') -----
buildPluggableButton: aSpec
^self subclassResponsibility!
----- Method: ToolBuilder>>buildPluggableCheckBox: (in category 'widgets optional') -----
buildPluggableCheckBox: spec
^self buildPluggableButton: spec!
----- Method: ToolBuilder>>buildPluggableCodePane: (in category 'widgets optional') -----
buildPluggableCodePane: aSpec
^self buildPluggableText: aSpec!
----- Method: ToolBuilder>>buildPluggableDropDownList: (in category 'widgets optional') -----
buildPluggableDropDownList: spec
^self buildPluggableList: spec!
----- Method: ToolBuilder>>buildPluggableInputField: (in category 'widgets optional') -----
buildPluggableInputField: aSpec
^self buildPluggableText: aSpec!
----- Method: ToolBuilder>>buildPluggableList: (in category 'widgets required') -----
buildPluggableList: aSpec
^self subclassResponsibility!
----- Method: ToolBuilder>>buildPluggableMenu: (in category 'widgets required') -----
buildPluggableMenu: menuSpec
self subclassResponsibility.!
----- Method: ToolBuilder>>buildPluggableMenuItem: (in category 'widgets required') -----
buildPluggableMenuItem: menuSpec
self subclassResponsibility.!
----- Method: ToolBuilder>>buildPluggableMultiSelectionList: (in category 'widgets optional') -----
buildPluggableMultiSelectionList: aSpec
^self buildPluggableList: aSpec!
----- Method: ToolBuilder>>buildPluggablePanel: (in category 'widgets required') -----
buildPluggablePanel: aSpec
^self subclassResponsibility!
----- Method: ToolBuilder>>buildPluggableRadioButton: (in category 'widgets optional') -----
buildPluggableRadioButton: spec
^self buildPluggableButton: spec!
----- Method: ToolBuilder>>buildPluggableScrollPane: (in category 'widgets optional') -----
buildPluggableScrollPane: spec
^ spec children
ifNotNil: [self buildPluggablePanel: spec]
ifNil: [spec morph ifNil: [spec morphClass new]]!
----- Method: ToolBuilder>>buildPluggableSpacer: (in category 'widgets required') -----
buildPluggableSpacer: aSpec
^ self subclassResponsibility!
----- Method: ToolBuilder>>buildPluggableText: (in category 'widgets required') -----
buildPluggableText: aSpec
^self subclassResponsibility!
----- Method: ToolBuilder>>buildPluggableTree: (in category 'widgets required') -----
buildPluggableTree: aSpec
^self subclassResponsibility!
----- Method: ToolBuilder>>buildPluggableWindow: (in category 'widgets required') -----
buildPluggableWindow: aSpec
^self subclassResponsibility!
----- Method: ToolBuilder>>close: (in category 'opening') -----
close: aWidget
"Close a previously opened widget"
^self subclassResponsibility!
----- Method: ToolBuilder>>initialize (in category 'initialize') -----
initialize
!
----- Method: ToolBuilder>>open: (in category 'opening') -----
open: anObject
"Build and open the object. Answer the widget opened."
^self subclassResponsibility!
----- Method: ToolBuilder>>open:label: (in category 'opening') -----
open: anObject label: aString
"Build an open the object, labeling it appropriately. Answer the widget opened."
^self subclassResponsibility!
----- Method: ToolBuilder>>openDebugger: (in category 'opening') -----
openDebugger: aSpec
"Build and open a debugger from the given spec.
Answer the widget opened. Subclasses can override this
method if opening a debugger has specific requirements
different from opening other widgets."
self open: aSpec
!
----- Method: ToolBuilder>>openDebugger:label: (in category 'opening') -----
openDebugger: aSpec label: aString
"Build and open a debugger from the given spec, labeling it appropriately.
Answer the widget opened. Subclasses can override this
method if opening a debugger has specific requirements
different from opening other widgets."
^self open: aSpec label: aString
!
----- Method: ToolBuilder>>openDebugger:label:closing: (in category 'opening') -----
openDebugger: aSpec label: aString closing: topView
"Build and open a debugger from the given spec, labeling it appropriately.
Answer the widget opened. Subclasses can override this
method if opening a debugger has specific requirements
different from opening other widgets."
self close: topView.
self open: aSpec label: aString
!
----- Method: ToolBuilder>>parent (in category 'accessing') -----
parent
^parent!
----- Method: ToolBuilder>>parent: (in category 'accessing') -----
parent: aWidget
parent := aWidget!
----- Method: ToolBuilder>>pluggableActionButtonSpec (in category 'defaults') -----
pluggableActionButtonSpec
^PluggableActionButtonSpec!
----- Method: ToolBuilder>>pluggableAlternateMultiSelectionListSpec (in category 'defaults') -----
pluggableAlternateMultiSelectionListSpec
^ PluggableAlternateMultiSelectionListSpec!
----- Method: ToolBuilder>>pluggableButtonSpec (in category 'defaults') -----
pluggableButtonSpec
^PluggableButtonSpec!
----- Method: ToolBuilder>>pluggableCheckBoxSpec (in category 'defaults') -----
pluggableCheckBoxSpec
^PluggableCheckBoxSpec!
----- Method: ToolBuilder>>pluggableCodePaneSpec (in category 'defaults') -----
pluggableCodePaneSpec
^PluggableCodePaneSpec!
----- Method: ToolBuilder>>pluggableDropDownListSpec (in category 'defaults') -----
pluggableDropDownListSpec
^PluggableDropDownListSpec!
----- Method: ToolBuilder>>pluggableInputFieldSpec (in category 'defaults') -----
pluggableInputFieldSpec
^PluggableInputFieldSpec!
----- Method: ToolBuilder>>pluggableListSpec (in category 'defaults') -----
pluggableListSpec
^PluggableListSpec!
----- Method: ToolBuilder>>pluggableMenuSpec (in category 'defaults') -----
pluggableMenuSpec
^ PluggableMenuSpec!
----- Method: ToolBuilder>>pluggableMultiSelectionListSpec (in category 'defaults') -----
pluggableMultiSelectionListSpec
^PluggableMultiSelectionListSpec!
----- Method: ToolBuilder>>pluggablePanelSpec (in category 'defaults') -----
pluggablePanelSpec
^PluggablePanelSpec!
----- Method: ToolBuilder>>pluggableRadioButtonSpec (in category 'defaults') -----
pluggableRadioButtonSpec
^PluggableRadioButtonSpec!
----- Method: ToolBuilder>>pluggableScrollPaneSpec (in category 'defaults') -----
pluggableScrollPaneSpec
^ PluggableScrollPaneSpec!
----- Method: ToolBuilder>>pluggableSpacerSpec (in category 'defaults') -----
pluggableSpacerSpec
^ PluggableSpacerSpec!
----- Method: ToolBuilder>>pluggableTextSpec (in category 'defaults') -----
pluggableTextSpec
^PluggableTextSpec!
----- Method: ToolBuilder>>pluggableTreeSpec (in category 'defaults') -----
pluggableTreeSpec
^PluggableTreeSpec!
----- Method: ToolBuilder>>pluggableWindowSpec (in category 'defaults') -----
pluggableWindowSpec
^PluggableWindowSpec!
----- Method: ToolBuilder>>runModal: (in category 'opening') -----
runModal: aWidget
"Run the (previously opened) widget modally, e.g.,
do not return control to the sender before the user has responded."
^self subclassResponsibility!
----- Method: ToolBuilder>>widgetAt: (in category 'accessing') -----
widgetAt: widgetID
"Answer the widget with the given ID"
^self widgetAt: widgetID ifAbsent:[nil]!
----- Method: ToolBuilder>>widgetAt:ifAbsent: (in category 'accessing') -----
widgetAt: widgetID ifAbsent: aBlock
"Answer the widget with the given ID"
^aBlock value!
Object subclass: #ToolBuilderSpec
instanceVariableNames: 'name help'
classVariableNames: ''
poolDictionaries: ''
category: 'ToolBuilder-Kernel'!
!ToolBuilderSpec commentStamp: 'ar 2/11/2005 14:59' prior: 0!
I am an abstract widget specification. I can be rendered using many different UI frameworks.!
ToolBuilderSpec subclass: #PluggableMenuItemSpec
instanceVariableNames: 'label action checked enabled separator subMenu checkable'
classVariableNames: ''
poolDictionaries: ''
category: 'ToolBuilder-Kernel'!
----- Method: PluggableMenuItemSpec>>action (in category 'accessing') -----
action
"Answer the action associated with the receiver"
^action!
----- Method: PluggableMenuItemSpec>>action: (in category 'accessing') -----
action: aMessageSend
"Answer the action associated with the receiver"
action := aMessageSend!
----- Method: PluggableMenuItemSpec>>analyzeLabel (in category 'initialize') -----
analyzeLabel
"For Morphic compatiblity. Some labels include markup such as <on>, <off> etc.
Analyze the label for these annotations and take appropriate action."
| marker |
marker := label copyFrom: 1 to: (label indexOf: $>).
(marker = '<on>' or:[marker = '<yes>']) ifTrue:[
checkable := true.
checked := true.
label := label copyFrom: marker size+1 to: label size.
].
(marker = '<off>' or:[marker = '<no>']) ifTrue:[
checkable := true.
checked := false.
label := label copyFrom: marker size+1 to: label size.
].
!
----- Method: PluggableMenuItemSpec>>beCheckable (in category 'accessing') -----
beCheckable
checkable := true.!
----- Method: PluggableMenuItemSpec>>buildWith: (in category 'building') -----
buildWith: builder
^ builder buildPluggableMenuItem: self!
----- Method: PluggableMenuItemSpec>>checked (in category 'accessing') -----
checked
"Answer whether the receiver is checked"
^checked ifNil:[false]!
----- Method: PluggableMenuItemSpec>>checked: (in category 'accessing') -----
checked: aBool
"Indicate whether the receiver is checked"
checked := aBool.!
----- Method: PluggableMenuItemSpec>>enabled (in category 'accessing') -----
enabled
"Answer whether the receiver is enabled"
^enabled ifNil:[true]!
----- Method: PluggableMenuItemSpec>>enabled: (in category 'accessing') -----
enabled: aBool
"Indicate whether the receiver is enabled"
enabled := aBool!
----- Method: PluggableMenuItemSpec>>initialize (in category 'initialize') -----
initialize
checkable := false.!
----- Method: PluggableMenuItemSpec>>isCheckable (in category 'accessing') -----
isCheckable
^ checkable!
----- Method: PluggableMenuItemSpec>>label (in category 'accessing') -----
label
"Answer the receiver's label"
^label!
----- Method: PluggableMenuItemSpec>>label: (in category 'accessing') -----
label: aString
"Set the receiver's label"
label := aString!
----- Method: PluggableMenuItemSpec>>separator (in category 'accessing') -----
separator
"Answer whether the receiver should be followed by a separator"
^separator ifNil:[false]!
----- Method: PluggableMenuItemSpec>>separator: (in category 'accessing') -----
separator: aBool
"Indicate whether the receiver should be followed by a separator"
separator := aBool.!
----- Method: PluggableMenuItemSpec>>subMenu (in category 'accessing') -----
subMenu
"Answer the receiver's subMenu"
^subMenu!
----- Method: PluggableMenuItemSpec>>subMenu: (in category 'accessing') -----
subMenu: aMenuSpec
"Answer the receiver's subMenu"
subMenu := aMenuSpec!
ToolBuilderSpec subclass: #PluggableMenuSpec
instanceVariableNames: 'label model items'
classVariableNames: ''
poolDictionaries: ''
category: 'ToolBuilder-Kernel'!
----- Method: PluggableMenuSpec class>>withModel: (in category 'as yet unclassified') -----
withModel: aModel
^ self new model: aModel!
----- Method: PluggableMenuSpec>>add:action: (in category 'construction') -----
add: aString action: aMessageSend
| item |
item := self addMenuItem.
item label: aString.
item action: aMessageSend.
^item!
----- Method: PluggableMenuSpec>>add:target:selector:argumentList: (in category 'construction') -----
add: aString target: anObject selector: aSelector argumentList: anArray
^self add: aString action: (MessageSend
receiver: anObject
selector: aSelector
arguments: anArray).!
----- Method: PluggableMenuSpec>>addList: (in category 'construction') -----
addList: aList
"Add the given items to this menu, where each item is a pair (<string> <actionSelector>).. If an element of the list is simply the symobl $-, add a line to the receiver. The optional third element of each entry, if present, provides balloon help."
aList do: [:tuple |
(tuple == #-)
ifTrue: [self addSeparator]
ifFalse:[ | item |
item := self add: tuple first target: model selector: tuple second argumentList: #().
tuple size > 2 ifTrue:[item help: tuple third]]]!
----- Method: PluggableMenuSpec>>addMenuItem (in category 'construction') -----
addMenuItem
| item |
item := self newMenuItem.
self items add: item.
^item!
----- Method: PluggableMenuSpec>>addSeparator (in category 'construction') -----
addSeparator
self items isEmpty ifTrue:[^nil].
self items last separator: true.!
----- Method: PluggableMenuSpec>>analyzeItemLabels (in category 'construction') -----
analyzeItemLabels
"Analyze the item labels"
items do:[:item| item analyzeLabel].
!
----- Method: PluggableMenuSpec>>buildWith: (in category 'construction') -----
buildWith: builder
self analyzeItemLabels.
^ builder buildPluggableMenu: self!
----- Method: PluggableMenuSpec>>items (in category 'accessing') -----
items
^ items ifNil: [items := OrderedCollection new]!
----- Method: PluggableMenuSpec>>label (in category 'accessing') -----
label
^label!
----- Method: PluggableMenuSpec>>label: (in category 'accessing') -----
label: aString
label := aString.!
----- Method: PluggableMenuSpec>>model (in category 'accessing') -----
model
^ model!
----- Method: PluggableMenuSpec>>model: (in category 'accessing') -----
model: anObject
model := anObject!
----- Method: PluggableMenuSpec>>newMenuItem (in category 'construction') -----
newMenuItem
^PluggableMenuItemSpec new!
ToolBuilderSpec subclass: #PluggableWidgetSpec
instanceVariableNames: 'model frame color minimumExtent margin padding horizontalResizing verticalResizing'
classVariableNames: ''
poolDictionaries: ''
category: 'ToolBuilder-Kernel'!
!PluggableWidgetSpec commentStamp: 'ar 2/9/2005 18:40' prior: 0!
The abstract superclass for all widgets.
Instance variables:
model <Object> The object the various requests should be directed to.
frame <Rectangle> The associated layout frame for this object (if any).
!
PluggableWidgetSpec subclass: #PluggableButtonSpec
instanceVariableNames: 'action label state enabled style changeLabelWhen'
classVariableNames: ''
poolDictionaries: ''
category: 'ToolBuilder-Kernel'!
!PluggableButtonSpec commentStamp: 'ar 2/11/2005 21:57' prior: 0!
A button, both for firing as well as used in radio-button style (e.g., carrying a selection).
Instance variables:
action <Symbol> The action to perform when the button is fired.
label <Symbol|String> The selector for retrieving the button's label or label directly.
state <Symbol> The selector for retrieving the button's selection state.
enabled <Symbo> The selector for retrieving the button's enabled state.
color <Symbo> The selector for retrieving the button color.
help <String> The balloon help for the button.!
PluggableButtonSpec subclass: #PluggableActionButtonSpec
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
category: 'ToolBuilder-Kernel'!
!PluggableActionButtonSpec commentStamp: 'dtl 9/19/2011 07:51' prior: 0!
PluggableActionButtonSpec is intended as a HINT for the builder that this widget will be used as push (action) button. Unless explicitly supported it will be automatically substituted by PluggableButton.!
----- Method: PluggableActionButtonSpec>>buildWith: (in category 'building') -----
buildWith: builder
^builder buildPluggableActionButton: self!
----- Method: PluggableButtonSpec>>action (in category 'accessing') -----
action
"Answer the action to be performed by the receiver"
^action!
----- Method: PluggableButtonSpec>>action: (in category 'accessing') -----
action: aSymbol
"Indicate the action to be performed by the receiver"
action := aSymbol!
----- Method: PluggableButtonSpec>>buildWith: (in category 'building') -----
buildWith: builder
^builder buildPluggableButton: self!
----- Method: PluggableButtonSpec>>changeLabelWhen (in category 'accessing') -----
changeLabelWhen
"When handled in in an update: handler, treat this symbol as notification
that the button label should be updated."
^changeLabelWhen!
----- Method: PluggableButtonSpec>>changeLabelWhen: (in category 'accessing') -----
changeLabelWhen: aSymbol
"When the button handles aSymbol in its update: handler, treat it
as notification that the button label should be updated."
changeLabelWhen := aSymbol!
----- Method: PluggableButtonSpec>>enabled (in category 'accessing') -----
enabled
"Answer the selector for retrieving the button's enablement"
^enabled ifNil:[true]!
----- Method: PluggableButtonSpec>>enabled: (in category 'accessing') -----
enabled: aSymbol
"Indicate the selector for retrieving the button's enablement"
enabled := aSymbol!
----- Method: PluggableButtonSpec>>label (in category 'accessing') -----
label
"Answer the label (or the selector for retrieving the label)"
^label!
----- Method: PluggableButtonSpec>>label: (in category 'accessing') -----
label: aSymbol
"Indicate the selector for retrieving the label"
label := aSymbol.!
----- Method: PluggableButtonSpec>>state (in category 'accessing') -----
state
"Answer the selector for retrieving the button's state"
^state!
----- Method: PluggableButtonSpec>>state: (in category 'accessing') -----
state: aSymbol
"Indicate the selector for retrieving the button's state"
state := aSymbol.!
----- Method: PluggableButtonSpec>>style (in category 'accessing') -----
style
"Treat aSymbol as a hint to modify the button appearance."
^style
!
----- Method: PluggableButtonSpec>>style: (in category 'accessing') -----
style: aSymbol
"Use aSymbol as a hint to modify the button appearance."
style := aSymbol
!
PluggableButtonSpec subclass: #PluggableCheckBoxSpec
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
category: 'ToolBuilder-Kernel'!
!PluggableCheckBoxSpec commentStamp: 'ar 2/12/2005 23:13' prior: 0!
PluggableCheckBox is intended as a HINT for the builder that this widget will be used as check box. Unless explicitly supported it will be automatically substituted by PluggableButton.!
----- Method: PluggableCheckBoxSpec>>buildWith: (in category 'building') -----
buildWith: builder
^builder buildPluggableCheckBox: self!
PluggableButtonSpec subclass: #PluggableRadioButtonSpec
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
category: 'ToolBuilder-Kernel'!
!PluggableRadioButtonSpec commentStamp: 'ar 2/12/2005 23:14' prior: 0!
PluggableRadioButton is intended as a HINT for the builder that this widget will be used as radio button. Unless explicitly supported it will be automatically substituted by PluggableButton.!
----- Method: PluggableRadioButtonSpec>>buildWith: (in category 'building') -----
buildWith: builder
^builder buildPluggableRadioButton: self!
PluggableWidgetSpec subclass: #PluggableCompositeSpec
instanceVariableNames: 'children layout wantsResizeHandles spacing'
classVariableNames: ''
poolDictionaries: ''
category: 'ToolBuilder-Kernel'!
!PluggableCompositeSpec commentStamp: 'ar 2/11/2005 21:58' prior: 0!
A composite user interface element.
Instance variables:
children <Symbol|Collection> Symbol to retrieve children or children directly
layout <Symbol> The layout for this composite.
!
----- Method: PluggableCompositeSpec>>children (in category 'accessing') -----
children
"Answer the selector to retrieve this panel's children"
^children!
----- Method: PluggableCompositeSpec>>children: (in category 'accessing') -----
children: aSymbol
"Indicate the selector to retrieve this panel's children"
children := aSymbol!
----- Method: PluggableCompositeSpec>>layout (in category 'accessing') -----
layout
"Answer the symbol indicating the layout of the composite:
#proportional (default): Use frames as appropriate.
#horizontal: Arrange the elements horizontally
#vertical: Arrange the elements vertically.
"
^layout ifNil:[#proportional]!
----- Method: PluggableCompositeSpec>>layout: (in category 'accessing') -----
layout: aSymbol
"Answer the symbol indicating the layout of the composite:
#proportional (default): Use frames as appropriate.
#horizontal: Arrange the elements horizontally
#vertical: Arrange the elements vertically.
"
layout := aSymbol!
----- Method: PluggableCompositeSpec>>spacing (in category 'layout hints') -----
spacing
"...between components of this widget."
^ spacing!
----- Method: PluggableCompositeSpec>>spacing: (in category 'layout hints') -----
spacing: numberOrPoint
spacing := numberOrPoint.!
----- Method: PluggableCompositeSpec>>wantsResizeHandles (in category 'accessing') -----
wantsResizeHandles
^ wantsResizeHandles!
----- Method: PluggableCompositeSpec>>wantsResizeHandles: (in category 'accessing') -----
wantsResizeHandles: aBoolean
wantsResizeHandles := aBoolean.!
PluggableCompositeSpec subclass: #PluggablePanelSpec
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
category: 'ToolBuilder-Kernel'!
!PluggablePanelSpec commentStamp: 'ar 2/11/2005 15:01' prior: 0!
A panel with a (possibly changing) set of child elements. Expects to see change/update notifications when the childrens change.!
----- Method: PluggablePanelSpec>>buildWith: (in category 'building') -----
buildWith: builder
^builder buildPluggablePanel: self.!
PluggableCompositeSpec subclass: #PluggableScrollPaneSpec
instanceVariableNames: 'morph morphClass borderWidth vScrollBarPolicy hScrollBarPolicy'
classVariableNames: ''
poolDictionaries: ''
category: 'ToolBuilder-Kernel'!
----- Method: PluggableScrollPaneSpec>>borderWidth (in category 'accessing') -----
borderWidth
^ borderWidth ifNil: [1]!
----- Method: PluggableScrollPaneSpec>>borderWidth: (in category 'accessing') -----
borderWidth: anObject
borderWidth := anObject!
----- Method: PluggableScrollPaneSpec>>buildWith: (in category 'building') -----
buildWith: builder
^ builder buildPluggableScrollPane: self!
----- Method: PluggableScrollPaneSpec>>hScrollBarPolicy (in category 'accessing') -----
hScrollBarPolicy
^ hScrollBarPolicy ifNil: [#always]!
----- Method: PluggableScrollPaneSpec>>hScrollBarPolicy: (in category 'accessing') -----
hScrollBarPolicy: anObject
"#always, #never, #whenNeeded"
hScrollBarPolicy := anObject!
----- Method: PluggableScrollPaneSpec>>morph (in category 'accessing') -----
morph
^ morph!
----- Method: PluggableScrollPaneSpec>>morph: (in category 'accessing') -----
morph: anObject
morph := anObject!
----- Method: PluggableScrollPaneSpec>>morphClass (in category 'accessing') -----
morphClass
^ morphClass!
----- Method: PluggableScrollPaneSpec>>morphClass: (in category 'accessing') -----
morphClass: anObject
morphClass := anObject!
----- Method: PluggableScrollPaneSpec>>vScrollBarPolicy (in category 'accessing') -----
vScrollBarPolicy
^ vScrollBarPolicy ifNil: [#always]!
----- Method: PluggableScrollPaneSpec>>vScrollBarPolicy: (in category 'accessing') -----
vScrollBarPolicy: anObject
"#always, #never, #whenNeeded"
vScrollBarPolicy := anObject!
PluggableCompositeSpec subclass: #PluggableWindowSpec
instanceVariableNames: 'label extent closeAction isDialog multiWindowStyle'
classVariableNames: ''
poolDictionaries: ''
category: 'ToolBuilder-Kernel'!
!PluggableWindowSpec commentStamp: '<historical>' prior: 0!
A common window. Expects to see change/update notifications when the label should change.
Instance variables:
label <String|Symbol> The selector under which to retrieve the label or the label directly
extent <Point> The (initial) extent of the window.
closeAction <Symbol> The action to perform when the window is closed.!
----- Method: PluggableWindowSpec>>buildWith: (in category 'building') -----
buildWith: builder
^builder buildPluggableWindow: self.!
----- Method: PluggableWindowSpec>>closeAction (in category 'accessing') -----
closeAction
"Answer the receiver's closeAction"
^closeAction!
----- Method: PluggableWindowSpec>>closeAction: (in category 'accessing') -----
closeAction: aSymbol
"Answer the receiver's closeAction"
closeAction := aSymbol.!
----- Method: PluggableWindowSpec>>extent (in category 'accessing') -----
extent
"Answer the window's (initial) extent"
^extent!
----- Method: PluggableWindowSpec>>extent: (in category 'accessing') -----
extent: aPoint
"Indicate the window's (initial) extent"
extent := aPoint!
----- Method: PluggableWindowSpec>>isDialog (in category 'accessing') -----
isDialog
^isDialog ifNil: [false]
!
----- Method: PluggableWindowSpec>>isDialog: (in category 'accessing') -----
isDialog: val
isDialog := val
!
----- Method: PluggableWindowSpec>>label (in category 'accessing') -----
label
"Answer the selector for retrieving the window's label"
^label!
----- Method: PluggableWindowSpec>>label: (in category 'accessing') -----
label: aString
"Indicate the selector for retrieving the window's label"
label := aString!
----- Method: PluggableWindowSpec>>multiWindowStyle (in category 'accessing') -----
multiWindowStyle
"Answer the value of multiWindowStyle, a Symbol or nil"
^multiWindowStyle!
----- Method: PluggableWindowSpec>>multiWindowStyle: (in category 'accessing') -----
multiWindowStyle: aSymbol
"Set the value of multiWindowStyle, one of #labelButton or #tabbed"
multiWindowStyle := aSymbol!
PluggableWidgetSpec subclass: #PluggableDropDownListSpec
instanceVariableNames: 'listSelector selectionSelector selectionSetter'
classVariableNames: ''
poolDictionaries: ''
category: 'ToolBuilder-Kernel'!
----- Method: PluggableDropDownListSpec>>buildWith: (in category 'building') -----
buildWith: builder
^builder buildPluggableDropDownList: self!
----- Method: PluggableDropDownListSpec>>listSelector (in category 'accessing') -----
listSelector
"Answer the value of listSelector"
^ listSelector!
----- Method: PluggableDropDownListSpec>>listSelector: (in category 'accessing') -----
listSelector: anObject
"Set the value of listSelector"
listSelector := anObject!
----- Method: PluggableDropDownListSpec>>selectionSelector (in category 'accessing') -----
selectionSelector
"Answer the value of selectionSelector"
^ selectionSelector!
----- Method: PluggableDropDownListSpec>>selectionSelector: (in category 'accessing') -----
selectionSelector: anObject
"Set the value of selectionSelector"
selectionSelector := anObject!
----- Method: PluggableDropDownListSpec>>selectionSetter (in category 'accessing') -----
selectionSetter
"Answer the value of selectionSetter"
^ selectionSetter!
----- Method: PluggableDropDownListSpec>>selectionSetter: (in category 'accessing') -----
selectionSetter: anObject
"Set the value of selectionSetter"
selectionSetter := anObject!
PluggableWidgetSpec subclass: #PluggableListSpec
instanceVariableNames: 'list getIndex setIndex getSelected setSelected menu keyPress autoDeselect dragItem dropItem dropAccept doubleClick listSize listItem keystrokePreview icon vScrollBarPolicy hScrollBarPolicy'
classVariableNames: ''
poolDictionaries: ''
category: 'ToolBuilder-Kernel'!
!PluggableListSpec commentStamp: 'ar 7/15/2005 11:54' prior: 0!
A single selection list element.
Instance variables:
list <Symbol> The selector to retrieve the list elements.
getIndex <Symbol> The selector to retrieve the list selection index.
setIndex <Symbol> The selector to set the list selection index.
getSelected <Symbol> The selector to retrieve the list selection.
setSelected <Symbol> The selector to set the list selection.
menu <Symbol> The selector to offer (to retrieve?) the context menu.
keyPress <Symbol> The selector to invoke for handling keyboard shortcuts.
autoDeselect <Boolean> Whether the list should allow automatic deselection or not.
dragItem <Symbol> Selector to initiate a drag action on an item
dropItem <Symbol> Selector to initiate a drop action of an item
dropAccept <Symbol> Selector to determine whether a drop would be accepted!
----- Method: PluggableListSpec>>autoDeselect (in category 'accessing') -----
autoDeselect
"Answer whether this tree can be automatically deselected"
^autoDeselect ifNil:[true]!
----- Method: PluggableListSpec>>autoDeselect: (in category 'accessing') -----
autoDeselect: aBool
"Indicate whether this tree can be automatically deselected"
autoDeselect := aBool!
----- Method: PluggableListSpec>>buildWith: (in category 'building') -----
buildWith: builder
^builder buildPluggableList: self!
----- Method: PluggableListSpec>>doubleClick (in category 'accessing') -----
doubleClick
"Answer the selector to perform when a double-click occurs"
^doubleClick!
----- Method: PluggableListSpec>>doubleClick: (in category 'accessing') -----
doubleClick: aSymbol
"Set the selector to perform when a double-click occurs"
doubleClick := aSymbol.!
----- Method: PluggableListSpec>>dragItem (in category 'accessing') -----
dragItem
"Answer the selector for dragging an item"
^dragItem!
----- Method: PluggableListSpec>>dragItem: (in category 'accessing') -----
dragItem: aSymbol
"Set the selector for dragging an item"
dragItem := aSymbol!
----- Method: PluggableListSpec>>dropAccept (in category 'accessing') -----
dropAccept
"Answer the selector to determine whether a drop would be accepted"
^dropAccept!
----- Method: PluggableListSpec>>dropAccept: (in category 'accessing') -----
dropAccept: aSymbol
"Answer the selector to determine whether a drop would be accepted"
dropAccept := aSymbol.!
----- Method: PluggableListSpec>>dropItem (in category 'accessing') -----
dropItem
"Answer the selector for dropping an item"
^dropItem!
----- Method: PluggableListSpec>>dropItem: (in category 'accessing') -----
dropItem: aSymbol
"Set the selector for dropping an item"
dropItem := aSymbol!
----- Method: PluggableListSpec>>getIndex (in category 'accessing') -----
getIndex
"Answer the selector for retrieving the list's selection index"
^getIndex!
----- Method: PluggableListSpec>>getIndex: (in category 'accessing') -----
getIndex: aSymbol
"Indicate the selector for retrieving the list's selection index"
getIndex := aSymbol!
----- Method: PluggableListSpec>>getSelected (in category 'accessing') -----
getSelected
"Answer the selector for retrieving the list selection"
^getSelected!
----- Method: PluggableListSpec>>getSelected: (in category 'accessing') -----
getSelected: aSymbol
"Indicate the selector for retrieving the list selection"
getSelected := aSymbol!
----- Method: PluggableListSpec>>hScrollBarPolicy (in category 'accessing') -----
hScrollBarPolicy
^ hScrollBarPolicy!
----- Method: PluggableListSpec>>hScrollBarPolicy: (in category 'accessing') -----
hScrollBarPolicy: aSymbol
"#always, #never, #whenNeeded"
hScrollBarPolicy := aSymbol.!
----- Method: PluggableListSpec>>icon (in category 'accessing') -----
icon
^ icon!
----- Method: PluggableListSpec>>icon: (in category 'accessing') -----
icon: aSelector
icon := aSelector!
----- Method: PluggableListSpec>>keyPress (in category 'accessing') -----
keyPress
"Answer the selector for invoking the list's keyPress handler"
^keyPress!
----- Method: PluggableListSpec>>keyPress: (in category 'accessing') -----
keyPress: aSymbol
"Indicate the selector for invoking the list's keyPress handler"
keyPress := aSymbol!
----- Method: PluggableListSpec>>keystrokePreview (in category 'accessing') -----
keystrokePreview
"Answer the selector to determine whether to allow the model a preview of keystrokes"
^ keystrokePreview!
----- Method: PluggableListSpec>>keystrokePreview: (in category 'accessing') -----
keystrokePreview: aSymbol
"The selector to determine whether to allow the model a preview of keystrokes"
keystrokePreview := aSymbol.!
----- Method: PluggableListSpec>>list (in category 'accessing') -----
list
"Answer the selector for retrieving the list contents"
^list!
----- Method: PluggableListSpec>>list: (in category 'accessing') -----
list: aSymbol
"Indicate the selector for retrieving the list contents"
list := aSymbol.!
----- Method: PluggableListSpec>>listItem (in category 'accessing') -----
listItem
"Answer the selector for retrieving the list element"
^listItem!
----- Method: PluggableListSpec>>listItem: (in category 'accessing') -----
listItem: aSymbol
"Indicate the selector for retrieving the list element"
listItem := aSymbol.!
----- Method: PluggableListSpec>>listSize (in category 'accessing') -----
listSize
"Answer the selector for retrieving the list size"
^listSize!
----- Method: PluggableListSpec>>listSize: (in category 'accessing') -----
listSize: aSymbol
"Indicate the selector for retrieving the list size"
listSize := aSymbol.!
----- Method: PluggableListSpec>>menu (in category 'accessing') -----
menu
"Answer the selector for retrieving the list's menu"
^menu!
----- Method: PluggableListSpec>>menu: (in category 'accessing') -----
menu: aSymbol
"Indicate the selector for retrieving the list's menu"
menu := aSymbol!
----- Method: PluggableListSpec>>setIndex (in category 'accessing') -----
setIndex
"Answer the selector for setting the list's selection index"
^setIndex!
----- Method: PluggableListSpec>>setIndex: (in category 'accessing') -----
setIndex: aSymbol
"Answer the selector for setting the list's selection index"
setIndex := aSymbol!
----- Method: PluggableListSpec>>setSelected (in category 'accessing') -----
setSelected
"Answer the selector for setting the list selection"
^setSelected!
----- Method: PluggableListSpec>>setSelected: (in category 'accessing') -----
setSelected: aSymbol
"Indicate the selector for setting the list selection"
setSelected := aSymbol!
----- Method: PluggableListSpec>>vScrollBarPolicy (in category 'accessing') -----
vScrollBarPolicy
^ vScrollBarPolicy!
----- Method: PluggableListSpec>>vScrollBarPolicy: (in category 'accessing') -----
vScrollBarPolicy: aSymbol
"#always, #never, #whenNeeded"
vScrollBarPolicy := aSymbol.!
PluggableListSpec subclass: #PluggableMultiSelectionListSpec
instanceVariableNames: 'getSelectionList setSelectionList'
classVariableNames: ''
poolDictionaries: ''
category: 'ToolBuilder-Kernel'!
!PluggableMultiSelectionListSpec commentStamp: 'ar 2/12/2005 13:31' prior: 0!
PluggableMultiSelectionListSpec specifies a list with multiple selection behavior.
Instance variables:
getSelectionList <Symbol> The message to retrieve the multiple selections.
setSelectionList <Symbol> The message to indicate multiple selections.!
PluggableMultiSelectionListSpec subclass: #PluggableAlternateMultiSelectionListSpec
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
category: 'ToolBuilder-Kernel'!
----- Method: PluggableAlternateMultiSelectionListSpec>>buildWith: (in category 'building') -----
buildWith: builder
^ builder buildPluggableAlternateMultiSelectionList: self!
----- Method: PluggableMultiSelectionListSpec>>buildWith: (in category 'building') -----
buildWith: builder
^builder buildPluggableMultiSelectionList: self!
----- Method: PluggableMultiSelectionListSpec>>getSelectionList (in category 'accessing') -----
getSelectionList
"Answer the message to retrieve the multiple selections"
^getSelectionList!
----- Method: PluggableMultiSelectionListSpec>>getSelectionList: (in category 'accessing') -----
getSelectionList: aSymbol
"Indicate the message to retrieve the multiple selections"
getSelectionList := aSymbol!
----- Method: PluggableMultiSelectionListSpec>>setSelectionList (in category 'accessing') -----
setSelectionList
"Answer the message to indicate multiple selections"
^setSelectionList!
----- Method: PluggableMultiSelectionListSpec>>setSelectionList: (in category 'accessing') -----
setSelectionList: aSymbol
"Indicate the message to indicate multiple selections"
setSelectionList := aSymbol!
PluggableWidgetSpec subclass: #PluggableSpacerSpec
instanceVariableNames: 'extent'
classVariableNames: ''
poolDictionaries: ''
category: 'ToolBuilder-Kernel'!
----- Method: PluggableSpacerSpec>>buildWith: (in category 'building') -----
buildWith: builder
^builder buildPluggableSpacer: self!
----- Method: PluggableSpacerSpec>>color (in category 'accessing') -----
color
^ super color ifNil: [Color transparent]!
----- Method: PluggableSpacerSpec>>extent (in category 'layout hints') -----
extent
^ extent ifNil: [5@5]!
----- Method: PluggableSpacerSpec>>extent: (in category 'layout hints') -----
extent: aPoint
extent := aPoint.!
----- Method: PluggableSpacerSpec>>fillSpaceHorizontally (in category 'convenience') -----
fillSpaceHorizontally
self horizontalResizing: #spaceFill.!
----- Method: PluggableSpacerSpec>>fillSpaceVertically (in category 'convenience') -----
fillSpaceVertically
self verticalResizing: #spaceFill.!
----- Method: PluggableSpacerSpec>>horizontalResizing (in category 'accessing') -----
horizontalResizing
^ super horizontalResizing ifNil: [#rigid]!
----- Method: PluggableSpacerSpec>>verticalResizing (in category 'accessing') -----
verticalResizing
^ super verticalResizing ifNil: [#rigid]!
PluggableWidgetSpec subclass: #PluggableTextSpec
instanceVariableNames: 'getText setText selection menu askBeforeDiscardingEdits editText indicateUnacceptedChanges stylerClass font readOnly softLineWrap hardLineWrap'
classVariableNames: ''
poolDictionaries: ''
category: 'ToolBuilder-Kernel'!
!PluggableTextSpec commentStamp: 'ar 2/11/2005 21:58' prior: 0!
A text editor.
Instance variables:
getText <Symbol> The selector to retrieve the text.
setText <Symbol> The selector to set the text.
selection <Symbol> The selector to retrieve the text selection.
menu <Symbol> The selector to offer (to retrieve?) the context menu.
color <Symbol> The selector to retrieve the background color.
!
PluggableTextSpec subclass: #PluggableCodePaneSpec
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
category: 'ToolBuilder-Kernel'!
!PluggableCodePaneSpec commentStamp: 'ar 8/18/2009 00:02' prior: 0!
A PluggableTextSpec specifically intended to edit code. Uses Syntax-Highlighting.!
----- Method: PluggableCodePaneSpec>>buildWith: (in category 'building') -----
buildWith: builder
^builder buildPluggableCodePane: self!
----- Method: PluggableCodePaneSpec>>font (in category 'accessing') -----
font
^ font ifNil: [Preferences standardCodeFont]!
----- Method: PluggableCodePaneSpec>>stylerClass (in category 'accessing') -----
stylerClass
^ super stylerClass ifNil: [Smalltalk classNamed: 'SHTextStylerST80']!
PluggableTextSpec subclass: #PluggableInputFieldSpec
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
category: 'ToolBuilder-Kernel'!
!PluggableInputFieldSpec commentStamp: 'ar 2/12/2005 23:13' prior: 0!
PluggableInputField is intended as a HINT for the builder that this widget will be used as a single line input field. Unless explicitly supported it will be automatically substituted by PluggableText.!
----- Method: PluggableInputFieldSpec>>buildWith: (in category 'building') -----
buildWith: builder
^builder buildPluggableInputField: self!
----- Method: PluggableInputFieldSpec>>hardLineWrap (in category 'accessing') -----
hardLineWrap
^ false!
----- Method: PluggableInputFieldSpec>>softLineWrap (in category 'accessing') -----
softLineWrap
^ super softLineWrap ifNil: [false]!
----- Method: PluggableTextSpec>>askBeforeDiscardingEdits (in category 'accessing') -----
askBeforeDiscardingEdits
^askBeforeDiscardingEdits ifNil:[true]!
----- Method: PluggableTextSpec>>askBeforeDiscardingEdits: (in category 'accessing') -----
askBeforeDiscardingEdits: aBool
askBeforeDiscardingEdits := aBool!
----- Method: PluggableTextSpec>>buildWith: (in category 'building') -----
buildWith: builder
^builder buildPluggableText: self!
----- Method: PluggableTextSpec>>editText (in category 'accessing') -----
editText
^ editText!
----- Method: PluggableTextSpec>>editText: (in category 'accessing') -----
editText: aSymbol
"Answer the selector for getting informed about any modifications of the text."
editText := aSymbol!
----- Method: PluggableTextSpec>>font (in category 'accessing') -----
font
^ font ifNil: [Preferences standardDefaultTextFont]!
----- Method: PluggableTextSpec>>font: (in category 'accessing') -----
font: aFont
font := aFont.!
----- Method: PluggableTextSpec>>getText (in category 'accessing') -----
getText
"Answer the selector for retrieving the text"
^getText!
----- Method: PluggableTextSpec>>getText: (in category 'accessing') -----
getText: aSymbol
"Answer the selector for retrieving the text"
getText := aSymbol!
----- Method: PluggableTextSpec>>hardLineWrap (in category 'accessing') -----
hardLineWrap
^ hardLineWrap!
----- Method: PluggableTextSpec>>hardLineWrap: (in category 'accessing') -----
hardLineWrap: aBoolean
hardLineWrap := aBoolean.!
----- Method: PluggableTextSpec>>indicateUnacceptedChanges (in category 'accessing') -----
indicateUnacceptedChanges
^ indicateUnacceptedChanges ifNil: [true]!
----- Method: PluggableTextSpec>>indicateUnacceptedChanges: (in category 'accessing') -----
indicateUnacceptedChanges: aBoolean
indicateUnacceptedChanges := aBoolean.!
----- Method: PluggableTextSpec>>menu (in category 'accessing') -----
menu
"Answer the selector for retrieving the text's menu"
^menu!
----- Method: PluggableTextSpec>>menu: (in category 'accessing') -----
menu: aSymbol
"Indicate the selector for retrieving the text's menu"
menu := aSymbol!
----- Method: PluggableTextSpec>>readOnly (in category 'accessing') -----
readOnly
^ readOnly ifNil: [false]!
----- Method: PluggableTextSpec>>readOnly: (in category 'accessing') -----
readOnly: aBoolean
readOnly := aBoolean.!
----- Method: PluggableTextSpec>>selection (in category 'accessing') -----
selection
"Answer the selector for retrieving the text selection"
^selection!
----- Method: PluggableTextSpec>>selection: (in category 'accessing') -----
selection: aSymbol
"Indicate the selector for retrieving the text selection"
selection := aSymbol!
----- Method: PluggableTextSpec>>setText (in category 'accessing') -----
setText
"Answer the selector for setting the text"
^setText!
----- Method: PluggableTextSpec>>setText: (in category 'accessing') -----
setText: aSymbol
"Answer the selector for setting the text"
setText := aSymbol!
----- Method: PluggableTextSpec>>softLineWrap (in category 'accessing') -----
softLineWrap
^ softLineWrap!
----- Method: PluggableTextSpec>>softLineWrap: (in category 'accessing') -----
softLineWrap: aBoolean
softLineWrap := aBoolean.!
----- Method: PluggableTextSpec>>stylerClass (in category 'accessing') -----
stylerClass
^ stylerClass!
----- Method: PluggableTextSpec>>stylerClass: (in category 'accessing') -----
stylerClass: aStylerClass
stylerClass := aStylerClass.!
PluggableWidgetSpec subclass: #PluggableTreeSpec
instanceVariableNames: 'roots getSelectedPath setSelected getSelected setSelectedParent getChildren hasChildren label icon unusedVar menu keyPress wantsDrop dropItem dropAccept autoDeselect dragItem nodeClass columns'
classVariableNames: ''
poolDictionaries: ''
category: 'ToolBuilder-Kernel'!
!PluggableTreeSpec commentStamp: 'mvdg 3/21/2008 20:59' prior: 0!
A pluggable tree widget. PluggableTrees are slightly different from lists in such that they ALWAYS store the actual objects and use the label selector to query for the label of the item. PluggableTrees also behave somewhat differently in such that they do not have a "getSelected" message but only a getSelectedPath message. The difference is that getSelectedPath is used to indicate by the model that the tree should select the appropriate path. This allows disambiguation of items. Because of this, implementations of PluggableTrees must always set their internal selection directly, e.g., rather than sending the model a setSelected message and wait for an update of the #getSelected the implementation must set the selection before sending the #setSelected message. If a client doesn't want this, it can always just signal a change of getSelectedPath to revert to whatever is needed.
Instance variables:
roots <Symbol> The message to retrieve the roots of the tree.
getSelectedPath <Symbol> The message to retrieve the selected path in the tree.
setSelected <Symbol> The message to set the selected item in the tree.
getChildren <Symbol> The message to retrieve the children of an item
hasChildren <Symbol> The message to query for children of an item
label <Symbol> The message to query for the label of an item.
icon <Symbol> The message to query for the icon of an item.
help <Symbol> The message to query for the help of an item.
menu <Symbol> The message to query for the tree's menu
keyPress <Symbol> The message to process a keystroke.
wantsDrop <Symbol> The message to query whether a drop might be accepted.
dropItem <Symbol> The message to drop an item.
enableDrag <Boolean> Enable dragging from this tree.
autoDeselect <Boolean> Whether the tree should allow automatic deselection or not.
unusedVar (unused) This variable is a placeholder to fix problems with loading packages in 3.10.!
----- Method: PluggableTreeSpec>>autoDeselect (in category 'accessing') -----
autoDeselect
"Answer whether this tree can be automatically deselected"
^autoDeselect ifNil:[true]!
----- Method: PluggableTreeSpec>>autoDeselect: (in category 'accessing') -----
autoDeselect: aBool
"Indicate whether this tree can be automatically deselected"
autoDeselect := aBool.!
----- Method: PluggableTreeSpec>>buildWith: (in category 'building') -----
buildWith: builder
^builder buildPluggableTree: self!
----- Method: PluggableTreeSpec>>columns (in category 'accessing') -----
columns
^ columns!
----- Method: PluggableTreeSpec>>columns: (in category 'accessing') -----
columns: columnSpecs
columns := columnSpecs.!
----- Method: PluggableTreeSpec>>dragItem (in category 'accessing') -----
dragItem
^ dragItem.!
----- Method: PluggableTreeSpec>>dragItem: (in category 'accessing') -----
dragItem: aSymbol
"Set the selector for dragging an item"
dragItem := aSymbol!
----- Method: PluggableTreeSpec>>dropAccept (in category 'accessing') -----
dropAccept
"Answer the selector for querying the receiver about accepting drops"
^dropAccept!
----- Method: PluggableTreeSpec>>dropAccept: (in category 'accessing') -----
dropAccept: aSymbol
"Set the selector for querying the receiver about accepting drops"
dropAccept := aSymbol!
----- Method: PluggableTreeSpec>>dropItem (in category 'accessing') -----
dropItem
"Answer the selector for invoking the tree's dragDrop handler"
^dropItem!
----- Method: PluggableTreeSpec>>dropItem: (in category 'accessing') -----
dropItem: aSymbol
"Indicate the selector for invoking the tree's dragDrop handler"
dropItem := aSymbol!
----- Method: PluggableTreeSpec>>getChildren (in category 'accessing') -----
getChildren
"Answer the message to get the children of this tree"
^getChildren!
----- Method: PluggableTreeSpec>>getChildren: (in category 'accessing') -----
getChildren: aSymbol
"Indicate the message to retrieve the children of this tree"
getChildren := aSymbol!
----- Method: PluggableTreeSpec>>getSelected (in category 'accessing') -----
getSelected
^ getSelected!
----- Method: PluggableTreeSpec>>getSelected: (in category 'accessing') -----
getSelected: aSymbol
"Indicate a single node in the tree. Only works if that node is visible, too. Use #getSelectedPath otherwise."
getSelected := aSymbol.!
----- Method: PluggableTreeSpec>>getSelectedPath (in category 'accessing') -----
getSelectedPath
"Answer the message to retrieve the selection of this tree"
^getSelectedPath!
----- Method: PluggableTreeSpec>>getSelectedPath: (in category 'accessing') -----
getSelectedPath: aSymbol
"Indicate the message to retrieve the selection of this tree"
getSelectedPath := aSymbol!
----- Method: PluggableTreeSpec>>hasChildren (in category 'accessing') -----
hasChildren
"Answer the message to get the existence of children in this tree"
^hasChildren!
----- Method: PluggableTreeSpec>>hasChildren: (in category 'accessing') -----
hasChildren: aSymbol
"Indicate the message to retrieve the existence children in this tree"
hasChildren := aSymbol!
----- Method: PluggableTreeSpec>>icon (in category 'accessing') -----
icon
"Answer the message to get the icons of this tree"
^icon!
----- Method: PluggableTreeSpec>>icon: (in category 'accessing') -----
icon: aSymbol
"Indicate the message to retrieve the icon of this tree"
icon := aSymbol!
----- Method: PluggableTreeSpec>>keyPress (in category 'accessing') -----
keyPress
"Answer the selector for invoking the tree's keyPress handler"
^keyPress!
----- Method: PluggableTreeSpec>>keyPress: (in category 'accessing') -----
keyPress: aSymbol
"Indicate the selector for invoking the tree's keyPress handler"
keyPress := aSymbol!
----- Method: PluggableTreeSpec>>label (in category 'accessing') -----
label
"Answer the message to get the labels of this tree"
^label!
----- Method: PluggableTreeSpec>>label: (in category 'accessing') -----
label: aSymbol
"Indicate the message to retrieve the labels of this tree"
label := aSymbol!
----- Method: PluggableTreeSpec>>menu (in category 'accessing') -----
menu
"Answer the message to get the menus of this tree"
^menu!
----- Method: PluggableTreeSpec>>menu: (in category 'accessing') -----
menu: aSymbol
"Indicate the message to retrieve the menus of this tree"
menu := aSymbol!
----- Method: PluggableTreeSpec>>nodeClass (in category 'accessing') -----
nodeClass
^ nodeClass!
----- Method: PluggableTreeSpec>>nodeClass: (in category 'accessing') -----
nodeClass: aListWrapperClass
nodeClass := aListWrapperClass.!
----- Method: PluggableTreeSpec>>roots (in category 'accessing') -----
roots
"Answer the message to retrieve the roots of this tree"
^roots!
----- Method: PluggableTreeSpec>>roots: (in category 'accessing') -----
roots: aSymbol
"Indicate the message to retrieve the roots of this tree"
roots := aSymbol!
----- Method: PluggableTreeSpec>>setSelected (in category 'accessing') -----
setSelected
"Answer the message to set the selection of this tree"
^setSelected!
----- Method: PluggableTreeSpec>>setSelected: (in category 'accessing') -----
setSelected: aSymbol
"Indicate the message to set the selection of this tree"
setSelected := aSymbol!
----- Method: PluggableTreeSpec>>setSelectedParent (in category 'accessing') -----
setSelectedParent
^ setSelectedParent!
----- Method: PluggableTreeSpec>>setSelectedParent: (in category 'accessing') -----
setSelectedParent: aSymbol
setSelectedParent := aSymbol!
----- Method: PluggableTreeSpec>>wantsDrop (in category 'accessing') -----
wantsDrop
"Answer the selector for invoking the tree's wantsDrop handler"
^wantsDrop!
----- Method: PluggableTreeSpec>>wantsDrop: (in category 'accessing') -----
wantsDrop: aSymbol
"Indicate the selector for invoking the tree's wantsDrop handler"
wantsDrop := aSymbol!
----- Method: PluggableWidgetSpec>>color (in category 'accessing') -----
color
"Answer the selector for retrieving the button's color"
^color!
----- Method: PluggableWidgetSpec>>color: (in category 'accessing') -----
color: aSymbol
"Indicate the selector for retrieving the button's color"
color := aSymbol!
----- Method: PluggableWidgetSpec>>frame (in category 'accessing') -----
frame
"Answer the receiver's layout frame"
^frame!
----- Method: PluggableWidgetSpec>>frame: (in category 'accessing') -----
frame: aRectangle
"Indicate the receiver's layout frame"
frame := aRectangle!
----- Method: PluggableWidgetSpec>>horizontalResizing (in category 'layout hints') -----
horizontalResizing
^ horizontalResizing!
----- Method: PluggableWidgetSpec>>horizontalResizing: (in category 'layout hints') -----
horizontalResizing: aSymbol
"#rigid, #spaceFill, #shrinkWrap"
horizontalResizing := aSymbol.!
----- Method: PluggableWidgetSpec>>margin (in category 'layout hints') -----
margin
"Space outside the widgets border.
See: http://www.w3.org/wiki/The_CSS_layout_model_-_boxes_borders_margins_padding"
^ margin!
----- Method: PluggableWidgetSpec>>margin: (in category 'layout hints') -----
margin: numberOrPointOrRectangle
margin := numberOrPointOrRectangle.!
----- Method: PluggableWidgetSpec>>minimumExtent (in category 'layout hints') -----
minimumExtent
^ minimumExtent ifNil: [-1 @ -1]!
----- Method: PluggableWidgetSpec>>minimumExtent: (in category 'layout hints') -----
minimumExtent: aPoint
minimumExtent := aPoint.!
----- Method: PluggableWidgetSpec>>minimumHeight (in category 'layout hints') -----
minimumHeight
^ self minimumExtent y!
----- Method: PluggableWidgetSpec>>minimumHeight: (in category 'layout hints') -----
minimumHeight: aNumber
self minimumExtent: self minimumExtent x @ aNumber.!
----- Method: PluggableWidgetSpec>>minimumWidth (in category 'layout hints') -----
minimumWidth
^ self minimumExtent x!
----- Method: PluggableWidgetSpec>>minimumWidth: (in category 'layout hints') -----
minimumWidth: aNumber
self minimumExtent: aNumber @ self minimumExtent y.!
----- Method: PluggableWidgetSpec>>model (in category 'accessing') -----
model
"Answer the model for which this widget should be built"
^model!
----- Method: PluggableWidgetSpec>>model: (in category 'accessing') -----
model: aModel
"Indicate the model for which this widget should be built"
model := aModel.!
----- Method: PluggableWidgetSpec>>padding (in category 'layout hints') -----
padding
"Space inside the widget's border.
See: http://www.w3.org/wiki/The_CSS_layout_model_-_boxes_borders_margins_padding"
^ padding!
----- Method: PluggableWidgetSpec>>padding: (in category 'layout hints') -----
padding: numberOrPointOrRectangle
padding := numberOrPointOrRectangle.!
----- Method: PluggableWidgetSpec>>verticalResizing (in category 'layout hints') -----
verticalResizing
^ verticalResizing!
----- Method: PluggableWidgetSpec>>verticalResizing: (in category 'layout hints') -----
verticalResizing: aSymbol
"#rigid, #spaceFill, #shrinkWrap"
verticalResizing := aSymbol.!
----- Method: ToolBuilderSpec>>buildWith: (in category 'building') -----
buildWith: aBuilder
^self subclassResponsibility!
----- Method: ToolBuilderSpec>>help (in category 'accessing') -----
help
"Answer the message to get the help texts of this element."
^ help!
----- Method: ToolBuilderSpec>>help: (in category 'accessing') -----
help: aSymbol
"Indicate the message to retrieve the help texts of this element."
help := aSymbol!
----- Method: ToolBuilderSpec>>name (in category 'accessing') -----
name
^ name!
----- Method: ToolBuilderSpec>>name: (in category 'accessing') -----
name: anObject
name := anObject!
Object subclass: #UIManager
instanceVariableNames: 'builderClass'
classVariableNames: ''
poolDictionaries: ''
category: 'ToolBuilder-Kernel'!
!UIManager commentStamp: 'dtl 5/2/2010 16:06' prior: 0!
UIManager is a dispatcher for various user interface requests, such as menu and dialog interactions. An instance of UIManager is associated with each Project to implement the appropriate functions for Morphic, MVC or other user interfaces.!
----- Method: UIManager class>>default (in category 'class initialization') -----
default
^ Project current uiManager!
----- Method: UIManager class>>getDefault (in category 'class initialization') -----
getDefault
"Ensure that a more specific manager can always be made by subclassing
a tool builder and implementing a more specific way of reacting to
#isActiveManager. For example, a BobsUIManager can subclass
MorphicUIManager and (if enabled, say Preferences useBobsUI) will
be considered before the parent (generic MorphicUIManager)."
^ (self allSubclasses
detect: [:any | any isActiveManager
and: [any subclasses
noneSatisfy: [:sub | sub isActiveManager]]]
ifNone: [])
ifNotNilDo: [:mgrClass | mgrClass new]!
----- Method: UIManager class>>isActiveManager (in category 'class initialization') -----
isActiveManager
"Answer whether I should act as the active ui manager"
^false!
----- Method: UIManager>>builderClass (in category 'builder') -----
builderClass
"Answer the kind of tool builder to use, possibly influenced by project preferences"
^ builderClass ifNil: [ builderClass := ToolBuilder findDefault ]!
----- Method: UIManager>>builderClass: (in category 'accessing') -----
builderClass: aClass
builderClass := aClass!
----- Method: UIManager>>chooseClassOrTrait (in category 'ui requests') -----
chooseClassOrTrait
"Let the user choose a Class or Trait"
^self chooseClassOrTrait: 'Class name or fragment?'!
----- Method: UIManager>>chooseClassOrTrait: (in category 'ui requests') -----
chooseClassOrTrait: label
"Let the user choose a Class or Trait"
^self chooseClassOrTrait: label from: Smalltalk environment!
----- Method: UIManager>>chooseClassOrTrait:from: (in category 'ui requests') -----
chooseClassOrTrait: label from: environment
"Let the user choose a Class or Trait."
| pattern |
pattern := self request: label.
^ self classOrTraitFrom: environment pattern: pattern label: label
!
----- Method: UIManager>>chooseDirectory (in category 'ui requests') -----
chooseDirectory
"Let the user choose a directory"
^self chooseDirectoryFrom: FileDirectory default!
----- Method: UIManager>>chooseDirectory: (in category 'ui requests') -----
chooseDirectory: label
"Let the user choose a directory"
^self chooseDirectory: label from: FileDirectory default!
----- Method: UIManager>>chooseDirectory:from: (in category 'ui requests') -----
chooseDirectory: label from: dir
"Let the user choose a directory"
^self subclassResponsibility!
----- Method: UIManager>>chooseDirectoryFrom: (in category 'ui requests') -----
chooseDirectoryFrom: dir
"Let the user choose a directory"
^self chooseDirectory: nil from: dir!
----- Method: UIManager>>chooseFileMatching: (in category 'ui requests') -----
chooseFileMatching: patterns
"Let the user choose a file matching the given patterns"
^self chooseFileMatching: patterns label: nil!
----- Method: UIManager>>chooseFileMatching:label: (in category 'ui requests') -----
chooseFileMatching: patterns label: labelString
"Let the user choose a file matching the given patterns"
^self subclassResponsibility!
----- Method: UIManager>>chooseFont:for:setSelector:getSelector: (in category 'ui requests') -----
chooseFont: titleString for: aModel setSelector: setSelector getSelector: getSelector
"Open a font-chooser for the given model"!
----- Method: UIManager>>chooseFrom: (in category 'ui requests') -----
chooseFrom: aList
"Choose an item from the given list. Answer the index of the selected item."
^self chooseFrom: aList lines: #()!
----- Method: UIManager>>chooseFrom:lines: (in category 'ui requests') -----
chooseFrom: aList lines: linesArray
"Choose an item from the given list. Answer the index of the selected item."
^self chooseFrom: aList lines: linesArray title: ''!
----- Method: UIManager>>chooseFrom:lines:title: (in category 'ui requests') -----
chooseFrom: aList lines: linesArray title: aString
"Choose an item from the given list. Answer the index of the selected item."
^self subclassResponsibility!
----- Method: UIManager>>chooseFrom:title: (in category 'ui requests') -----
chooseFrom: aList title: aString
"Choose an item from the given list. Answer the index of the selected item."
^self chooseFrom: aList lines: #() title: aString!
----- Method: UIManager>>chooseFrom:values: (in category 'ui requests') -----
chooseFrom: labelList values: valueList
"Choose an item from the given list. Answer the selected item."
^self chooseFrom: labelList values: valueList lines: #()!
----- Method: UIManager>>chooseFrom:values:lines: (in category 'ui requests') -----
chooseFrom: labelList values: valueList lines: linesArray
"Choose an item from the given list. Answer the selected item."
^self chooseFrom: labelList values: valueList lines: linesArray title: ''!
----- Method: UIManager>>chooseFrom:values:lines:title: (in category 'ui requests') -----
chooseFrom: labelList values: valueList lines: linesArray title: aString
"Choose an item from the given list. Answer the selected item."
^self subclassResponsibility!
----- Method: UIManager>>chooseFrom:values:title: (in category 'ui requests') -----
chooseFrom: labelList values: valueList title: aString
"Choose an item from the given list. Answer the selected item."
^self chooseFrom: labelList values: valueList lines: #() title: aString!
----- Method: UIManager>>chooseMultipleFrom: (in category 'ui requests') -----
chooseMultipleFrom: aList
"Choose one or more items from the given list. Answer the indices of the selected items."
^ self chooseMultipleFrom: aList lines: #()!
----- Method: UIManager>>chooseMultipleFrom:lines: (in category 'ui requests') -----
chooseMultipleFrom: aList lines: linesArray
"Choose one or more items from the given list. Answer the indices of the selected items."
^ self chooseMultipleFrom: aList lines: linesArray title: ''!
----- Method: UIManager>>chooseMultipleFrom:lines:title: (in category 'ui requests') -----
chooseMultipleFrom: aList lines: linesArray title: aString
"Choose one or more items from the given list. Answer the indices of the selected items."
^ (self chooseFrom: aList lines: linesArray title: aString) in: [:result |
result = 0 ifTrue: [#()] ifFalse: [{result}]]!
----- Method: UIManager>>chooseMultipleFrom:title: (in category 'ui requests') -----
chooseMultipleFrom: aList title: aString
"Choose one or more items from the given list. Answer the indices of the selected items."
^self chooseMultipleFrom: aList lines: #() title: aString!
----- Method: UIManager>>chooseMultipleFrom:values: (in category 'ui requests') -----
chooseMultipleFrom: labelList values: valueList
"Choose one or more items from the given list. Answer the selected items."
^ self chooseMultipleFrom: labelList values: valueList lines: #()!
----- Method: UIManager>>chooseMultipleFrom:values:lines: (in category 'ui requests') -----
chooseMultipleFrom: labelList values: valueList lines: linesArray
"Choose one or more items from the given list. Answer the selected items."
^ self chooseMultipleFrom: labelList values: valueList lines: linesArray title: ''!
----- Method: UIManager>>chooseMultipleFrom:values:lines:title: (in category 'ui requests') -----
chooseMultipleFrom: labelList values: valueList lines: linesArray title: aString
"Choose one or more items from the given list. Answer the selected items."
^ (self chooseFrom: labelList values: valueList lines: linesArray title: aString)
ifNil: [#()]
ifNotNil: [:resultValue | {resultValue}]!
----- Method: UIManager>>chooseMultipleFrom:values:title: (in category 'ui requests') -----
chooseMultipleFrom: labelList values: valueList title: aString
"Choose one or more items from the given list. Answer the selected items."
^ self chooseMultipleFrom: labelList values: valueList lines: #() title: aString!
----- Method: UIManager>>classFromPattern:withCaption: (in category 'system introspecting') -----
classFromPattern: pattern withCaption: aCaption
"If there is a class or trait whose name exactly given by pattern, return it.
If there is only one class or trait in the system whose name matches pattern, return it.
Otherwise, put up a menu offering the names of all classes that match pattern, and return the class chosen, else nil if nothing chosen.
This method ignores separator characters in the pattern"
^self classOrTraitFrom: Smalltalk environment pattern: pattern label: aCaption
"
self classFromPattern: 'CharRecog' withCaption: ''
self classFromPattern: 'rRecog' withCaption: ''
self classFromPattern: 'znak' withCaption: ''
self classFromPattern: 'orph' withCaption: ''
self classFromPattern: 'TCompil' withCaption: ''
"
!
----- Method: UIManager>>classOrTraitFrom:pattern:label: (in category 'system introspecting') -----
classOrTraitFrom: environment pattern: pattern label: label
"If there is a class or trait whose name exactly given by pattern, return it.
If there is only one class or trait in the given environment whose name matches pattern, return it.
Otherwise, put up a menu offering the names of all classes that match pattern, and return the class chosen, else nil if nothing chosen.
This method ignores separator characters in the pattern"
| toMatch potentialNames names exactMatch lines reducedIdentifiers selectedIndex |
toMatch := pattern copyWithoutAll: Character separators.
toMatch ifEmpty: [ ^nil ].
"If there's a class or trait named as pattern, then return it."
Symbol hasInterned: pattern ifTrue: [ :symbol |
environment at: symbol ifPresent: [ :maybeClassOrTrait |
((maybeClassOrTrait isKindOf: Class) or: [
maybeClassOrTrait isTrait ])
ifTrue: [ ^maybeClassOrTrait ] ] ].
"No exact match, look for potential matches."
toMatch := pattern asLowercase copyWithout: $..
potentialNames := (environment classAndTraitNames) asOrderedCollection.
names := pattern last = $. "This is some old hack, using String>>#match: may be better."
ifTrue: [ potentialNames select: [ :each | each asLowercase = toMatch ] ]
ifFalse: [
potentialNames select: [ :each |
each includesSubstring: toMatch caseSensitive: false ] ].
exactMatch := names detect: [ :each | each asLowercase = toMatch ] ifNone: [ nil ].
lines := OrderedCollection new.
exactMatch ifNotNil: [ lines add: 1 ].
"Also try some fuzzy matching."
reducedIdentifiers := pattern suggestedTypeNames select: [ :each |
potentialNames includes: each ].
reducedIdentifiers ifNotEmpty: [
names addAll: reducedIdentifiers.
lines add: 1 + names size + reducedIdentifiers size ].
"Let the user select if there's more than one possible match. This may give surprising results."
selectedIndex := names size = 1
ifTrue: [ 1 ]
ifFalse: [
exactMatch ifNotNil: [ names addFirst: exactMatch ].
self chooseFrom: names lines: lines title: label ].
selectedIndex = 0 ifTrue: [ ^nil ].
^environment at: (names at: selectedIndex) asSymbol!
----- Method: UIManager>>confirm: (in category 'ui requests') -----
confirm: queryString
"Put up a yes/no menu with caption queryString. Answer true if the
response is yes, false if no. This is a modal question--the user must
respond yes or no."
^self subclassResponsibility!
----- Method: UIManager>>confirm:orCancel: (in category 'ui requests') -----
confirm: aString orCancel: cancelBlock
"Put up a yes/no/cancel menu with caption aString. Answer true if
the response is yes, false if no. If cancel is chosen, evaluate
cancelBlock. This is a modal question--the user must respond yes or no."
^self subclassResponsibility!
----- Method: UIManager>>confirm:orCancel:title: (in category 'ui requests') -----
confirm: aString orCancel: cancelBlock title: titleString
"Put up a yes/no/cancel menu with caption aString, and titleString to label the dialog.
Answer true if the response is yes, false if no. If cancel is chosen, evaluate cancelBlock.
This is a modal question--the user must respond yes or no."
^self subclassResponsibility!
----- Method: UIManager>>confirm:title: (in category 'ui requests') -----
confirm: queryString title: titleString
"Put up a yes/no menu with caption queryString, and titleString to label the dialog.
Answer true if the response is yes, false if no. This is a modal question--the user
must respond yes or no."
^self subclassResponsibility!
----- Method: UIManager>>confirm:title:trueChoice:falseChoice: (in category 'ui requests') -----
confirm: queryString title: titleString trueChoice: trueChoice falseChoice: falseChoice
"Put up a yes/no menu with caption queryString, and titleString to label the dialog.
The actual wording for the two choices will be as provided in the trueChoice and
falseChoice parameters. Answer true if the response is the true-choice, false if it
is the false-choice. This is a modal question -- the user must respond one way or
the other."
^self subclassResponsibility!
----- Method: UIManager>>confirm:trueChoice:falseChoice: (in category 'ui requests') -----
confirm: queryString trueChoice: trueChoice falseChoice: falseChoice
"Put up a yes/no menu with caption queryString. The actual wording for the two choices will be as provided in the trueChoice and falseChoice parameters. Answer true if the response is the true-choice, false if it's the false-choice.
This is a modal question -- the user must respond one way or the other."
^self subclassResponsibility!
----- Method: UIManager>>displayProgress:at:from:to:during: (in category 'ui requests') -----
displayProgress: titleString at: aPoint from: minVal to: maxVal during: workBlock
"Display titleString as a caption over a progress bar while workBlock is evaluated."
^self subclassResponsibility!
----- Method: UIManager>>edit: (in category 'ui requests') -----
edit: aText
"Open an editor on the given string/text"
^self edit: aText label: nil!
----- Method: UIManager>>edit:label: (in category 'ui requests') -----
edit: aText label: labelString
"Open an editor on the given string/text"
^self edit: aText label: labelString accept: nil!
----- Method: UIManager>>edit:label:accept: (in category 'ui requests') -----
edit: aText label: labelString accept: anAction
"Open an editor on the given string/text"
^self subclassResponsibility!
----- Method: UIManager>>inform: (in category 'ui requests') -----
inform: aString
"Display a message for the user to read and then dismiss"
^self subclassResponsibility!
----- Method: UIManager>>informUser:during: (in category 'ui requests') -----
informUser: aString during: aBlock
"Display a message above (or below if insufficient room) the cursor
during execution of the given block.
UIManager default informUser: 'Just a sec!!' during: [(Delay forSeconds: 1) wait].
"
^self informUserDuring:[:bar| bar value: aString. aBlock value].!
----- Method: UIManager>>informUserDuring: (in category 'ui requests') -----
informUserDuring: aBlock
"Display a message above (or below if insufficient room) the cursor
during execution of the given block.
UIManager default informUserDuring:[:bar|
#(one two three) do:[:info|
bar value: info.
(Delay forSeconds: 1) wait]]"
^self subclassResponsibility!
----- Method: UIManager>>multiLineRequest:centerAt:initialAnswer:answerHeight: (in category 'ui requests') -----
multiLineRequest: queryString centerAt: aPoint initialAnswer: defaultAnswer answerHeight: answerHeight
"Create a multi-line instance of me whose question is queryString with
the given initial answer. Invoke it centered at the given point, and
answer the string the user accepts. Answer nil if the user cancels. An
empty string returned means that the ussr cleared the editing area and
then hit 'accept'. Because multiple lines are invited, we ask that the user
use the ENTER key, or (in morphic anyway) hit the 'accept' button, to
submit; that way, the return key can be typed to move to the next line."
^self subclassResponsibility!
----- Method: UIManager>>newDisplayDepthNoRestore: (in category 'display') -----
newDisplayDepthNoRestore: pixelSize
self subclassResponsibility.!
----- Method: UIManager>>request: (in category 'ui requests') -----
request: queryString
"Create an instance of me whose question is queryString. Invoke it
centered at the cursor, and answer the string the user accepts. Answer
the empty string if the user cancels."
^self request: queryString initialAnswer: ''!
----- Method: UIManager>>request:initialAnswer: (in category 'ui requests') -----
request: queryString initialAnswer: defaultAnswer
"Create an instance of me whose question is queryString with the given
initial answer. Invoke it centered at the given point, and answer the
string the user accepts. Answer the empty string if the user cancels."
^self subclassResponsibility!
----- Method: UIManager>>request:initialAnswer:centerAt: (in category 'ui requests') -----
request: queryString initialAnswer: defaultAnswer centerAt: aPoint
"Create an instance of me whose question is queryString with the given
initial answer. Invoke it centered at the given point, and answer the
string the user accepts. Answer the empty string if the user cancels."
^self subclassResponsibility!
----- Method: UIManager>>requestPassword: (in category 'ui requests') -----
requestPassword: queryString
"Create an instance of me whose question is queryString. Invoke it centered
at the cursor, and answer the string the user accepts. Answer the empty
string if the user cancels."
^self subclassResponsibility!
----- Method: UIManager>>restoreDisplay (in category 'display') -----
restoreDisplay
self subclassResponsibility.!
----- Method: UIManager>>restoreDisplayAfter: (in category 'display') -----
restoreDisplayAfter: aBlock
self subclassResponsibility.!
----- Method: UIManager>>screenBounds (in category 'accessing') -----
screenBounds
^ Display boundingBox!
----- Method: UIManager>>toolBuilder (in category 'builder') -----
toolBuilder
^ self builderClass new!
Chris Muller uploaded a new version of Help-Squeak-TerseGuide to project Squeak 4.6:
http://source.squeak.org/squeak46/Help-Squeak-TerseGuide-kfr.5.mcz
==================== Summary ====================
Name: Help-Squeak-TerseGuide-kfr.5
Author: kfr
Time: 7 May 2015, 9:38:42.385 am
UUID: 75a587af-6dbb-ee4b-b469-ad18259f34f8
Ancestors: Help-Squeak-TerseGuide-dhn.4
Expanded Rectangle guide a little
==================== Snapshot ====================
SystemOrganization addCategory: #'Help-Squeak-TerseGuide'!
CustomHelp subclass: #TerseGuideHelp
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
category: 'Help-Squeak-TerseGuide'!
!TerseGuideHelp commentStamp: 'dtl 11/26/2010 12:17' prior: 0!
TerseGuideHelp contains the Squeak terse guide by Chris Rathman (http://www.angelfire.com/tx4/cus/notes/smalltalk.html), maintained on the swiki at http://wiki.squeak.org/squeak/5699. Copyrights and credit for the original document belong to Chris Rathman.
The original document was formatted for printing with a fixed font. Here it is reformatted for display in a help browser, with various updates and corrections to match the current state of the Squeak image. The contents of the help pages may be evaluated as workspace expressions.
HelpBrowser openOn: TerseGuideHelp
!
----- Method: TerseGuideHelp class>>arithmetic (in category 'pages') -----
arithmetic
^HelpTopic
title: 'Arithmetic Expressions'
contents:
'"************************************************************************
* Arithmetic expressions: *
************************************************************************"
| x |
x := 6 + 3. "addition"
x := 6 - 3. "subtraction"
x := 6 * 3. "multiplication"
x := 1 + 2 * 3. "evaluation always left to right (1 + 2) * 3"
x := 5 / 3. "division with fractional result"
x := 5.0 / 3.0. "division with float result"
x := 5.0 // 3.0. "integer divide"
x := 5.0 \\ 3.0. "integer remainder"
x := -5. "unary minus"
x := 5 sign. "numeric sign (1, -1 or 0)"
x := 5 negated. "negate receiver"
x := 1.2 integerPart. "integer part of number (1.0)"
x := 1.2 fractionPart. "fractional part of number (0.2)"
x := 5 reciprocal. "reciprocal function"
x := 6 * 3.1. "auto convert to float"
x := 5 squared. "square function"
x := 25 sqrt. "square root"
x := 5 raisedTo: 2. "power function"
x := 5 raisedToInteger: 2. "power function with integer"
x := 5 exp. "exponential"
x := -5 abs. "absolute value"
x := 3.99 rounded. "round"
x := 3.99 truncated. "truncate"
x := 3.99 roundTo: 1. "round to specified decimal places"
x := 3.99 truncateTo: 1. "truncate to specified decimal places"
x := 3.99 floor. "truncate"
x := 3.99 ceiling. "round up"
x := 5 factorial. "factorial"
x := -5 quo: 3. "integer divide rounded toward zero"
x := -5 rem: 3. "integer remainder rounded toward zero"
x := 28 gcd: 12. "greatest common denominator"
x := 28 lcm: 12. "least common multiple"
x := 100 ln. "natural logarithm"
x := 100 log. "base 10 logarithm"
x := 100 log: 10 . "logarithm with specified base"
x := 100 floorLog: 10. "floor of the log"
x := 180 degreesToRadians. "convert degrees to radians"
x := 3.14 radiansToDegrees. "convert radians to degrees"
x := 0.7 sin. "sine"
x := 0.7 cos. "cosine"
x := 0.7 tan. "tangent"
x := 0.7 arcSin. "arcsine"
x := 0.7 arcCos. "arccosine"
x := 0.7 arcTan. "arctangent"
x := 10 max: 20. "get maximum of two numbers"
x := 10 min: 20. "get minimum of two numbers"
x := Float pi. "pi"
x := Float e. "exp constant"
x := Float infinity. "infinity"
x := Float nan. "not-a-number"
x := Random new next; yourself. x next. "random number stream (0.0 to 1.0)"
x := 100 atRandom. "quick random number"
'!
----- Method: TerseGuideHelp class>>array (in category 'pages') -----
array
^HelpTopic
title: 'Array'
contents:
'"************************************************************************
* Array: Fixed length collection *
* ByteArray: Array limited to byte elements (0-255) *
* WordArray: Array limited to word elements (0-2^32) *
************************************************************************"
| b x y sum max |
x := #(4 3 2 1). "constant array"
x := Array with: 5 with: 4 with: 3 with: 2. "create array with up to 4 elements"
x := Array new: 4. "allocate an array with specified size"
x "set array elements"
at: 1 put: 5;
at: 2 put: 4;
at: 3 put: 3;
at: 4 put: 2.
b := x isEmpty. "test if array is empty"
y := x size. "array size"
y := x at: 4. "get array element at index"
b := x includes: 3. "test if element is in array"
y := x copyFrom: 2 to: 4. "subarray"
y := x indexOf: 3 ifAbsent: [0]. "first position of element within array"
y := x occurrencesOf: 3. "number of times object in collection"
x do: [:a | Transcript show: a printString; cr]. "iterate over the array"
b := x allSatisfy: [:a | (a >= 1) & (a <= 4)]. "test if all elements meet condition"
y := x select: [:a | a > 2]. "return collection of elements that pass test"
y := x reject: [:a | a < 2]. "return collection of elements that fail test"
y := x collect: [:a | a + a]. "transform each element for new collection"
y := x detect: [:a | a > 3] ifNone: []. "return first element that passes test"
y := x findFirst: [:a | a < 3]. "find position of first element that passes test"
sum := 0. x do: [:a | sum := sum + a]. sum. "sum array elements"
sum := 0. 1 to: (x size) do: [:a | sum := sum + (x at: a)]. "sum array elements"
sum := x inject: 0 into: [:a :c | a + c]. "sum array elements"
max := x inject: 0 into: [:a :c | (a > c) "find max element in array"
ifTrue: [a]
ifFalse: [c]].
y := x shuffled. "randomly shuffle collection"
y := x asArray. "convert to array"
y := x asByteArray. "convert to byte array"
y := x asWordArray. "convert to word array"
y := x asOrderedCollection. "convert to ordered collection"
y := x asSortedCollection. "convert to sorted collection"
y := x asBag. "convert to bag collection"
y := x asSet. "convert to set collection"
'!
----- Method: TerseGuideHelp class>>assignment (in category 'pages') -----
assignment
^HelpTopic
title: 'Assignment'
contents:
'"************************************************************************
* Assignment: *
************************************************************************"
| x y z |
x _ 4. "assignment (Squeak) <-"
x := 5. "assignment"
x := y := z := 6. "compound assignment"
x := (y := 6) + 1.
x := Object new. "bind to allocated instance of a class"
x := 123 class. "discover the object class"
x := Integer superclass. "discover the superclass of a class"
x := Object allInstances. "get an array of all instances of a class"
x := Integer allSuperclasses. "get all superclasses of a class"
x := 1.2 hash. "hash value for object"
y := x copy. "copy object"
y := x shallowCopy. "copy object (not overridden)"
y := x deepCopy. "copy object and instance vars"
y := x veryDeepCopy. "complete tree copy using a dictionary"
'!
----- Method: TerseGuideHelp class>>association (in category 'pages') -----
association
^HelpTopic
title: 'Association'
contents:
'"************************************************************************
* Associations: *
************************************************************************"
| x y |
x := #myVar->''hello''.
y := x key.
y := x value.
'!
----- Method: TerseGuideHelp class>>bag (in category 'pages') -----
bag
^HelpTopic
title: 'Bag'
contents:
'"************************************************************************
* Bag: like OrderedCollection except elements are in no *
* particular order *
************************************************************************"
| b x y sum max |
x := Bag with: 4 with: 3 with: 2 with: 1. "create collection with up to 4 elements"
x := Bag new. "allocate collection"
x add: 4; add: 3; add: 1; add: 2; yourself. "add element to collection"
x add: 3 withOccurrences: 2. "add multiple copies to collection"
y := x addAll: #(7 8 9). "add multiple elements to collection"
y := x removeAll: #(7 8 9). "remove multiple elements from collection"
y := x remove: 4 ifAbsent: []. "remove element from collection"
b := x isEmpty. "test if empty"
y := x size. "number of elements"
b := x includes: 3. "test if element is in collection"
y := x occurrencesOf: 3. "number of times object in collection"
x do: [:a | Transcript show: a printString; cr]. "iterate over the collection"
b := x allSatisfy: [:a | (a >= 1) & (a <= 4)]. "test if all elements meet condition"
y := x select: [:a | a > 2]. "return collection of elements that pass test"
y := x reject: [:a | a < 2]. "return collection of elements that fail test"
y := x collect: [:a | a + a]. "transform each element for new collection"
y := x detect: [:a | a > 3] ifNone: []. "return first element that passes test"
sum := 0. x do: [:a | sum := sum + a]. sum. "sum elements"
sum := x inject: 0 into: [:a :c | a + c]. "sum elements"
max := x inject: 0 into: [:a :c | (a > c) "find max element in collection"
ifTrue: [a]
ifFalse: [c]].
y := x asOrderedCollection. "convert to ordered collection"
y := x asSortedCollection. "convert to sorted collection"
y := x asBag. "convert to bag collection"
y := x asSet. "convert to set collection"
'!
----- Method: TerseGuideHelp class>>bitwise (in category 'pages') -----
bitwise
^HelpTopic
title: 'Bitwise Manipulation'
contents:
'"************************************************************************
* Bitwise Manipulation: *
************************************************************************"
| b x |
x := 16rFF bitAnd: 16r0F. "and bits"
x := 16rF0 bitOr: 16r0F. "or bits"
x := 16rFF bitXor: 16r0F. "xor bits"
x := 16rFF bitInvert. "invert bits"
x := 16r0F bitShift: 4. "left shift"
x := 16rF0 bitShift: -4. "right shift"
x := 16r80 bitAt: 8. "bit at position (0|1)"
x := 16r80 highBit. "position of highest bit set"
b := 16rFF allMask: 16r0F. "test if all bits set in mask set in receiver"
b := 16rFF anyMask: 16r0F. "test if any bits set in mask set in receiver"
b := 16rFF noMask: 16r0F. "test if all bits set in mask clear in receiver"
'!
----- Method: TerseGuideHelp class>>block (in category 'pages') -----
block
^HelpTopic
title: 'Blocks'
contents:
'"************************************************************************
* Blocks: *
* - blocks are objects and may be assigned to a variable *
* - value is last expression evaluated unless explicit return *
* - blocks may be nested *
* - specification [ arguments | | localvars | expressions ] *
* - ^expression terminates block & method (exits all nested blocks) *
* - blocks intended for long term storage should not contain ^ *
************************************************************************"
| x y z fac |
x := [ y := 1. z := 2. ]. x value. "simple block usage"
x := [ :argOne :argTwo | argOne, '' and '' , argTwo.]. "set up block with argument passing"
Transcript show: (x value: ''First'' value: ''Second''); cr. "use block with argument passing"
x := [:e | | v | v := 1. e + v] value: 2. "localvar in a block"
fac := [ :n | n > 1 ifTrue: [n * (fac value: n-1)] ifFalse: [1]]. "closure on block variable"
fac value: 5. "closure variable scoped to its block"
'!
----- Method: TerseGuideHelp class>>bookName (in category 'accessing') -----
bookName
^'Terse Guide'!
----- Method: TerseGuideHelp class>>boolean (in category 'pages') -----
boolean
^HelpTopic
title: 'Boolean'
contents:
'"************************************************************************
* Booleans: *
************************************************************************"
| b x y |
x := 1. y := 2.
b := (x = y). "equals"
b := (x ~= y). "not equals"
b := (x == y). "identical"
b := (x ~~ y). "not identical"
b := (x > y). "greater than"
b := (x < y). "less than"
b := (x >= y). "greater than or equal"
b := (x <= y). "less than or equal"
b := b not. "boolean not"
b := (x < 5) & (y > 1). "boolean and"
b := (x < 5) | (y > 1). "boolean or"
b := (x < 5) and: [y > 1]. "boolean and (short-circuit)"
b := (x < 5) or: [y > 1]. "boolean or (short-circuit)"
b := (x < 5) eqv: (y > 1). "test if both true or both false"
b := (x < 5) xor: (y > 1). "test if one true and other false"
b := 5 between: 3 and: 12. "between (inclusive)"
b := 123 isKindOf: Number. "test if object is class or subclass of"
b := 123 isMemberOf: SmallInteger. "test if object is type of class"
b := 123 respondsTo: #sqrt. "test if object responds to message"
b := x isNil. "test if object is nil"
b := x isZero. "test if number is zero"
b := x positive. "test if number is positive"
b := x strictlyPositive. "test if number is greater than zero"
b := x negative. "test if number is negative"
b := x even. "test if number is even"
b := x odd. "test if number is odd"
b := x isLiteral. "test if literal constant"
b := x isInteger. "test if object is integer"
b := x isFloat. "test if object is float"
b := x isNumber. "test if object is number"
b := $A isUppercase. "test if upper case character"
b := $A isLowercase. "test if lower case character"
'!
----- Method: TerseGuideHelp class>>character (in category 'pages') -----
character
^HelpTopic
title: 'Character'
contents:
'"************************************************************************
* Character: *
************************************************************************"
| x y b |
x := $A. "character assignment"
y := x isLowercase. "test if lower case"
y := x isUppercase. "test if upper case"
y := x isLetter. "test if letter"
y := x isDigit. "test if digit"
y := x isAlphaNumeric. "test if alphanumeric"
y := x isSeparator. "test if seperator char"
y := x isVowel. "test if vowel"
y := x digitValue. "convert to numeric digit value"
y := x asLowercase. "convert to lower case"
y := x asUppercase. "convert to upper case"
y := x asciiValue. "convert to numeric ascii value"
y := x asString. "convert to string"
b := $A <= $B. "comparison"
y := $A max: $B.
'!
----- Method: TerseGuideHelp class>>conditionalStatement (in category 'pages') -----
conditionalStatement
^HelpTopic
title: 'Conditional Statement'
contents:
'"************************************************************************
* Conditional Statements: *
************************************************************************"
| x switch result |
x := 11.
x > 10 ifTrue: [Transcript show: ''ifTrue''; cr]. "if then"
x > 10 ifFalse: [Transcript show: ''ifFalse''; cr]. "if else"
x > 10 "if then else"
ifTrue: [Transcript show: ''ifTrue''; cr]
ifFalse: [Transcript show: ''ifFalse''; cr].
x > 10 "if else then"
ifFalse: [Transcript show: ''ifFalse''; cr]
ifTrue: [Transcript show: ''ifTrue''; cr].
Transcript
show:
(x > 10
ifTrue: [''ifTrue'']
ifFalse: [''ifFalse'']);
cr.
Transcript "nested if then else"
show:
(x > 10
ifTrue: [x > 5
ifTrue: [''A'']
ifFalse: [''B'']]
ifFalse: [''C'']);
cr.
switch := Dictionary new. "switch functionality"
switch at: $A put: [Transcript show: ''Case A''; cr].
switch at: $B put: [Transcript show: ''Case B''; cr].
switch at: $C put: [Transcript show: ''Case C''; cr].
result := (switch at: $B) value.
'!
----- Method: TerseGuideHelp class>>constants (in category 'pages') -----
constants
^HelpTopic
title: 'Constants'
contents:
'"************************************************************************
* Constants: *
************************************************************************"
| b x |
b := true. "true constant"
b := false. "false constant"
x := nil. "nil object constant"
x := 1. "integer constants"
x := 3.14. "float constants"
x := 2e-2. "fractional constants"
x := 16r0F. "hex constant".
x := -1. "negative constants"
x := ''Hello''. "string constant"
x := ''I''''m here''. "single quote escape"
x := $A. "character constant"
x := $ . "character constant (space)"
x := #aSymbol. "symbol constants"
x := #(3 2 1). "array constants"
x := #(''abc'' 2 $a). "mixing of types allowed"
'!
----- Method: TerseGuideHelp class>>conversion (in category 'pages') -----
conversion
^HelpTopic
title: 'Conversion'
contents:
'"************************************************************************
* Conversion: *
************************************************************************"
| x |
x := 3.99 asInteger. "convert number to integer (truncates in Squeak)"
x := 3.99 asFraction. "convert number to fraction"
x := 3 asFloat. "convert number to float"
x := 65 asCharacter. "convert integer to character"
x := $A asciiValue. "convert character to integer"
x := 3.99 printString. "convert object to string via printOn:"
x := 3.99 storeString. "convert object to string via storeOn:"
x := 15 radix: 16. "convert to string in given base"
x := 15 printStringBase: 16.
x := 15 storeStringBase: 16.
'!
----- Method: TerseGuideHelp class>>date (in category 'pages') -----
date
^HelpTopic
title: 'Date'
contents:
'"************************************************************************
* Date: *
************************************************************************"
| x y b |
x := Date today. "create date for today"
x := Date dateAndTimeNow. "create date from current time/date"
x := Date readFromString: ''01/02/1999''. "create date from formatted string"
x := Date newDay: 12 month: #July year: 1999. "create date from parts"
x := Date fromDays: 36000. "create date from elapsed days since 1/1/1901"
y := Date dayOfWeek: #Monday. "day of week as int (1-7)"
y := Date indexOfMonth: #January. "month of year as int (1-12)"
y := Date daysInMonth: 2 forYear: 1996. "day of month as int (1-31)"
y := Date daysInYear: 1996. "days in year (365|366)"
y := Date nameOfDay: 1. "weekday name (#Monday,...)"
y := Date nameOfMonth: 1. "month name (#January,...)"
y := Date leapYear: 1996. "1 if leap year; 0 if not leap year"
y := x weekday. "day of week (#Monday,...)"
y := x previous: #Monday. "date for previous day of week"
y := x dayOfMonth. "day of month (1-31)"
y := x day. "day of year (1-366)"
y := x firstDayOfMonth. "day of year for first day of month"
y := x monthName. "month of year (#January,...)"
y := x monthIndex. "month of year (1-12)"
y := x daysInMonth. "days in month (1-31)"
y := x year. "year (19xx)"
y := x daysInYear. "days in year (365|366)"
y := x daysLeftInYear. "days left in year (364|365)"
y := x asSeconds. "seconds elapsed since 1/1/1901"
y := x addDays: 10. "add days to date object"
y := x subtractDays: 10. "subtract days to date object"
y := x subtractDate: (Date today). "subtract date (result in days)"
y := x printFormat: #(2 1 3 $/ 1 1). "print formatted date"
b := (x <= Date today). "comparison"
'!
----- Method: TerseGuideHelp class>>debugging (in category 'pages') -----
debugging
^HelpTopic
title: 'Debugging'
contents:
'"************************************************************************
* debugging: *
************************************************************************"
| a b x |
x := Object new.
x yourself. "returns receiver"
String browse. "browse specified class"
x inspect. "open object inspector window"
x confirm: ''Is this correct?''.
x halt. "breakpoint to open debugger window"
x halt: ''Halt message''.
x notify: ''Notify text''.
x error: ''Error string''. "open up error window with title"
x shouldNotImplement. "flag message should not be implemented"
x subclassResponsibility. "flag message as abstract"
x errorImproperStore. "flag an improper store into indexable object"
x errorNonIntegerIndex. "flag only integers should be used as index"
x errorSubscriptBounds: 13. "flag subscript out of bounds"
x primitiveFailed. "system primitive failed"
a := ''A1''. b := ''B2''. a become: b. "switch two objects"
Transcript show: a, b; cr.
x doesNotUnderstand: (Message selector: #foo). "flag message is not handled"
'!
----- Method: TerseGuideHelp class>>dictionary (in category 'pages') -----
dictionary
^HelpTopic
title: 'Dictionary'
contents:
'"************************************************************************
* Dictionary: *
* IdentityDictionary: uses identity test (== rather than =) *
************************************************************************"
| b x y sum max |
x := Dictionary new. "allocate collection"
x add: #a->4; add: #b->3; add: #c->1; add: #d->2; yourself. "add element to collection"
x at: #e put: 3. "set element at index"
b := x isEmpty. "test if empty"
y := x size. "number of elements"
y := x at: #a ifAbsent: []. "retrieve element at index"
y := x keyAtValue: 3 ifAbsent: []. "retrieve key for given value with error block"
y := x removeKey: #e ifAbsent: []. "remove element from collection"
b := x includes: 3. "test if element is in values collection"
b := x includesKey: #a. "test if element is in keys collection"
y := x occurrencesOf: 3. "number of times object in collection"
y := x keys. "set of keys"
y := x values. "bag of values"
x do: [:a | Transcript show: a printString; cr]. "iterate over the values collection"
x keysDo: [:a | Transcript show: a printString; cr]. "iterate over the keys collection"
x associationsDo: [:a | Transcript show: a printString; cr]. "iterate over the associations"
x keysAndValuesDo: [:aKey :aValue | Transcript "iterate over keys and values"
show: aKey printString; space;
show: aValue printString; cr].
b := x allSatisfy: [:a | (a >= 1) & (a <= 4)]. "test if all elements meet condition"
y := x select: [:a | a > 2]. "return collection of elements that pass test"
y := x reject: [:a | a < 2]. "return collection of elements that fail test"
y := x collect: [:a | a + a]. "transform each element for new collection"
y := x detect: [:a | a > 3] ifNone: []. "return first element that passes test"
sum := 0. x do: [:a | sum := sum + a]. sum. "sum elements"
sum := x inject: 0 into: [:a :c | a + c]. "sum elements"
max := x inject: 0 into: [:a :c | (a > c) "find max element in collection"
ifTrue: [a]
ifFalse: [c]].
y := x asArray. "convert to array"
y := x asOrderedCollection. "convert to ordered collection"
y := x asSortedCollection. "convert to sorted collection"
y := x asBag. "convert to bag collection"
y := x asSet. "convert to set collection"
Smalltalk at: #CMRGlobal put: ''CMR entry''. "put global in Smalltalk Dictionary"
x := Smalltalk at: #CMRGlobal. "read global from Smalltalk Dictionary"
Transcript show: (CMRGlobal printString). "entries are directly accessible by name"
Smalltalk keys do: [ :k | "print out all classes"
((Smalltalk at: k) isKindOf: Class)
ifFalse: [Transcript show: k printString; cr]].
Smalltalk at: #CMRDictionary put: (Dictionary new). "set up user defined dictionary"
CMRDictionary at: #MyVar1 put: ''hello1''. "put entry in dictionary"
CMRDictionary add: #MyVar2->''hello2''. "add entry to dictionary use key->value combo"
CMRDictionary size. "dictionary size"
CMRDictionary keys do: [ :k | "print out keys in dictionary"
Transcript show: k printString; cr].
CMRDictionary values do: [ :k | "print out values in dictionary"
Transcript show: k printString; cr].
CMRDictionary keysAndValuesDo: [:aKey :aValue | "print out keys and values"
Transcript
show: aKey printString;
space;
show: aValue printString;
cr].
CMRDictionary associationsDo: [:aKeyValue | "another iterator for printing key values"
Transcript show: aKeyValue printString; cr].
Smalltalk removeKey: #CMRGlobal ifAbsent: []. "remove entry from Smalltalk dictionary"
Smalltalk removeKey: #CMRDictionary ifAbsent: []. "remove user dictionary from Smalltalk dictionary"
'!
----- Method: TerseGuideHelp class>>dynamic (in category 'pages') -----
dynamic
^HelpTopic
title: 'Dynamic Message Calling/Compiling'
contents:
'"************************************************************************
* Dynamic Message Calling/Compiling: *
************************************************************************"
| receiver message result argument keyword1 keyword2 argument1 argument2 |
"unary message"
receiver := 5.
message := ''factorial'' asSymbol.
result := receiver perform: message.
result := Compiler evaluate: ((receiver storeString), '' '', message).
result := (Message new setSelector: message arguments: #()) sentTo: receiver.
"binary message"
receiver := 1.
message := ''+'' asSymbol.
argument := 2.
result := receiver perform: message withArguments: (Array with: argument).
result := Compiler evaluate: ((receiver storeString), '' '', message, '' '', (argument storeString)).
result := (Message new setSelector: message arguments: (Array with: argument)) sentTo: receiver.
"keyword messages"
receiver := 12.
keyword1 := ''between:'' asSymbol.
keyword2 := ''and:'' asSymbol.
argument1 := 10.
argument2 := 20.
result := receiver
perform: (keyword1, keyword2) asSymbol
withArguments: (Array with: argument1 with: argument2).
result := Compiler evaluate:
((receiver storeString), '' '', keyword1, (argument1 storeString) , '' '', keyword2, (argument2 storeString)).
result := (Message
new
setSelector: (keyword1, keyword2) asSymbol
arguments: (Array with: argument1 with: argument2))
sentTo: receiver.
'!
----- Method: TerseGuideHelp class>>fileStream (in category 'pages') -----
fileStream
^HelpTopic
title: 'File Stream'
contents:
'"************************************************************************
* FileStream: *
************************************************************************"
| b x ios |
ios := FileStream newFileNamed: ''ios.txt''.
ios nextPut: $H; cr.
ios nextPutAll: ''Hello File''; cr.
''Hello File'' printOn: ios.
''Hello File'' storeOn: ios.
ios close.
ios := FileStream oldFileNamed: ''ios.txt''.
[(x := ios nextLine) notNil]
whileTrue: [Transcript show: x; cr].
ios position: 3.
x := ios position.
x := ios next.
x := ios peek.
b := ios atEnd.
ios close.
'!
----- Method: TerseGuideHelp class>>internalStream (in category 'pages') -----
internalStream
^HelpTopic
title: 'Internal Stream'
contents:
'"************************************************************************
* Internal Stream: *
************************************************************************"
| b x ios |
ios := ReadStream on: ''Hello read stream''.
ios := ReadStream on: ''Hello read stream'' from: 1 to: 5.
[(x := ios nextLine) notNil]
whileTrue: [Transcript show: x; cr].
ios position: 3.
ios position.
x := ios next.
x := ios peek.
x := ios contents.
b := ios atEnd.
ios := ReadWriteStream on: ''Hello read stream''.
ios := ReadWriteStream on: ''Hello read stream'' from: 1 to: 5.
ios := ReadWriteStream with: ''Hello read stream''.
ios := ReadWriteStream with: ''Hello read stream'' from: 1 to: 10.
ios position: 0.
[(x := ios nextLine) notNil]
whileTrue: [Transcript show: x; cr].
ios position: 6.
ios position.
ios nextPutAll: ''Chris''.
x := ios next.
x := ios peek.
x := ios contents.
b := ios atEnd.
'!
----- Method: TerseGuideHelp class>>interval (in category 'pages') -----
interval
^HelpTopic
title: 'Interval'
contents:
'"************************************************************************
* Interval: *
************************************************************************"
| b x y sum max |
x := Interval from: 5 to: 10. "create interval object"
x := 5 to: 10.
x := Interval from: 5 to: 10 by: 2. "create interval object with specified increment"
x := 5 to: 10 by: 2.
b := x isEmpty. "test if empty"
y := x size. "number of elements"
x includes: 9. "test if element is in collection"
x do: [:k | Transcript show: k printString; cr]. "iterate over interval"
b := x allSatisfy: [:a | (a >= 1) & (a <= 4)]. "test if all elements meet condition"
y := x select: [:a | a > 7]. "return collection of elements that pass test"
y := x reject: [:a | a < 2]. "return collection of elements that fail test"
y := x collect: [:a | a + a]. "transform each element for new collection"
y := x detect: [:a | a > 3] ifNone: []. "return first element that passes test"
y := x findFirst: [:a | a > 6]. "find position of first element that passes test"
sum := 0. x do: [:a | sum := sum + a]. sum. "sum elements"
sum := 0. 1 to: (x size) do: [:a | sum := sum + (x at: a)]. "sum elements"
sum := x inject: 0 into: [:a :c | a + c]. "sum elements"
max := x inject: 0 into: [:a :c | (a > c) "find max element in collection"
ifTrue: [a]
ifFalse: [c]].
y := x asArray. "convert to array"
y := x asOrderedCollection. "convert to ordered collection"
y := x asSortedCollection. "convert to sorted collection"
y := x asBag. "convert to bag collection"
y := x asSet. "convert to set collection"
'!
----- Method: TerseGuideHelp class>>introduction (in category 'pages') -----
introduction
^HelpTopic
title: 'General'
contents:
'"************************************************************************
* Allowable characters: *
* - a-z *
* - A-Z *
* - 0-9 *
* - .+/\*~<>@%|&? *
* - blank, tab, cr, ff, lf *
* *
* Variables: *
* - variables must be declared before use *
* - shared vars must begin with uppercase *
* - local vars must begin with lowercase *
* - reserved names: nil, true, false, self, super, and Smalltalk *
* *
* Variable scope: *
* - Global: defined in Dictionary Smalltalk and accessible by all *
* objects in system *
* - Special: (reserved) Smalltalk, super, self, true, false, & nil *
* - Method Temporary: local to a method *
* - Block Temporary: local to a block *
* - Pool: variables in a Dictionary object *
* - Method Parameters: automatic local vars created as a result of *
* message call with params *
* - Block Parameters: automatic local vars created as a result of *
* value: message call *
* - Class: shared with all instances of one class & its subclasses *
* - Class Instance: unique to each instance of a class *
* - Instance Variables: unique to each instance *
************************************************************************"
"Comments are enclosed in quotes"
"Period (.) is the statement seperator"
'!
----- Method: TerseGuideHelp class>>iterationStatement (in category 'pages') -----
iterationStatement
^HelpTopic
title: 'Iteration Statement'
contents:
'"************************************************************************
* Iteration statements: *
************************************************************************"
| x y |
x := 4. y := 1.
[x > 0] whileTrue: [x := x - 1. y := y * 2]. "while true loop"
[x >= 4] whileFalse: [x := x + 1. y := y * 2]. "while false loop"
x timesRepeat: [y := y * 2]. "times repear loop (i := 1 to x)"
1 to: x do: [:a | y := y * 2]. "for loop"
1 to: x by: 2 do: [:a | y := y / 2]. "for loop with specified increment"
#(5 4 3) do: [:a | x := x + a]. "iterate over array elements"
'!
----- Method: TerseGuideHelp class>>metaclass (in category 'pages') -----
metaclass
^HelpTopic
title: 'Class / Metaclass'
contents:
'"************************************************************************
* class/meta-class: *
************************************************************************"
| b x |
x := String name. "class name"
x := String category. "organization category"
x := String comment. "class comment"
x := String kindOfSubclass. "subclass type - subclass: variableSubclass, etc"
x := String definition. "class definition"
x := String instVarNames. "immediate instance variable names"
x := String allInstVarNames. "accumulated instance variable names"
x := String classVarNames. "immediate class variable names"
x := String allClassVarNames. "accumulated class variable names"
x := String sharedPools. "immediate dictionaries used as shared pools"
x := String allSharedPools. "accumulated dictionaries used as shared pools"
x := String selectors. "message selectors for class"
x := String sourceCodeAt: #indexOf:. "source code for specified method"
x := String allInstances. "collection of all instances of class"
x := String superclass. "immediate superclass"
x := String allSuperclasses. "accumulated superclasses"
x := String withAllSuperclasses. "receiver class and accumulated superclasses"
x := String subclasses. "immediate subclasses"
x := String allSubclasses. "accumulated subclasses"
x := String withAllSubclasses. "receiver class and accumulated subclasses"
b := String instSize. "number of named instance variables"
b := String isFixed. "true if no indexed instance variables"
b := String isVariable. "true if has indexed instance variables"
b := String isPointers. "true if index instance vars contain objects"
b := String isBits. "true if index instance vars contain bytes/words"
b := String isBytes. "true if index instance vars contain bytes"
b := String isWords. "true if index instance vars contain words"
Object withAllSubclasses size. "get total number of class entries"
'!
----- Method: TerseGuideHelp class>>methodCall (in category 'pages') -----
methodCall
^HelpTopic
title: 'Method Call'
contents:
'"************************************************************************
* Method calls: *
* - unary methods are messages with no arguments *
* - binary methods *
* - keyword methods are messages with selectors including colons *
* *
* standard categories/protocols: *
* - initialize-release (methods called for new instance) *
* - accessing (get/set methods) *
* - testing (boolean tests - is) *
* - comparing (boolean tests with parameter *
* - displaying (gui related methods) *
* - printing (methods for printing) *
* - updating (receive notification of changes) *
* - private (methods private to class) *
* - instance-creation (class methods for creating instance) *
************************************************************************"
| x |
x := 2 sqrt. "unary message"
x := 2 raisedTo: 10. "keyword message"
x := 194 * 9. "binary message"
Transcript show: (194 * 9) printString; cr. "combination (chaining)"
x := 2 perform: #sqrt. "indirect method invocation"
Transcript "Cascading - send multiple messages to receiver"
show: ''hello '';
show: ''world'';
cr.
x := 3 + 2; * 100. "result=300. Sends message to same receiver (3)"
'!
----- Method: TerseGuideHelp class>>misc (in category 'pages') -----
misc
^HelpTopic
title: 'Miscellaneous'
contents:
'"************************************************************************
* Misc. *
************************************************************************"
| x |
"Smalltalk condenseChanges." "compress the change file"
x := FillInTheBlank request: ''Prompt Me''. "prompt user for input"
x := UIManager default request: ''Prompt Me''. "prompt user for input using a flexible UI dispatcher"
Utilities openCommandKeyHelp
'!
----- Method: TerseGuideHelp class>>orderedCollection (in category 'pages') -----
orderedCollection
^HelpTopic
title: 'Ordered Collection'
contents:
'"************************************************************************
* OrderedCollection: acts like an expandable array *
************************************************************************"
| b x y sum max |
x := OrderedCollection with: 4 with: 3 with: 2 with: 1. "create collection with up to 4 elements"
x := OrderedCollection new. "allocate collection"
x add: 3; add: 2; add: 1; add: 4; yourself. "add element to collection"
y := x addFirst: 5. "add element at beginning of collection"
y := x removeFirst. "remove first element in collection"
y := x addLast: 6. "add element at end of collection"
y := x removeLast. "remove last element in collection"
y := x addAll: #(7 8 9). "add multiple elements to collection"
y := x removeAll: #(7 8 9). "remove multiple elements from collection"
x at: 2 put: 3. "set element at index"
y := x remove: 5 ifAbsent: []. "remove element from collection"
b := x isEmpty. "test if empty"
y := x size. "number of elements"
y := x at: 2. "retrieve element at index"
y := x first. "retrieve first element in collection"
y := x last. "retrieve last element in collection"
b := x includes: 5. "test if element is in collection"
y := x copyFrom: 2 to: 3. "subcollection"
y := x indexOf: 3 ifAbsent: [0]. "first position of element within collection"
y := x occurrencesOf: 3. "number of times object in collection"
x do: [:a | Transcript show: a printString; cr]. "iterate over the collection"
b := x allSatisfy: [:a | (a >= 1) & (a <= 4)]. "test if all elements meet condition"
y := x select: [:a | a > 2]. "return collection of elements that pass test"
y := x reject: [:a | a < 2]. "return collection of elements that fail test"
y := x collect: [:a | a + a]. "transform each element for new collection"
y := x detect: [:a | a > 3] ifNone: []. "return first element that passes test"
y := x findFirst: [:a | a < 2]. "find position of first element that passes test"
sum := 0. x do: [:a | sum := sum + a]. sum. "sum elements"
sum := 0. 1 to: (x size) do: [:a | sum := sum + (x at: a)]. "sum elements"
sum := x inject: 0 into: [:a :c | a + c]. "sum elements"
max := x inject: 0 into: [:a :c | (a > c) "find max element in collection"
ifTrue: [a]
ifFalse: [c]].
y := x shuffled. "randomly shuffle collection"
y := x asArray. "convert to array"
y := x asOrderedCollection. "convert to ordered collection"
y := x asSortedCollection. "convert to sorted collection"
y := x asBag. "convert to bag collection"
y := x asSet. "convert to set collection"
'!
----- Method: TerseGuideHelp class>>pages (in category 'accessing') -----
pages
^ #( introduction transcript assignment constants boolean arithmetic
bitwise conversion block methodCall conditionalStatement
iterationStatement character symbol string array orderedCollection
sortedCollection bag set interval association dictionary internalStream
fileStream date time point rectangle pen dynamic metaclass debugging
misc )!
----- Method: TerseGuideHelp class>>pen (in category 'pages') -----
pen
^HelpTopic
title: 'Pen'
contents:
'"************************************************************************
* Pen: *
************************************************************************"
| myPen |
Display restoreAfter: [
Display fillWhite.
myPen := Pen new. "get graphic pen"
myPen squareNib: 1.
myPen color: (Color blue). "set pen color"
myPen home. "position pen at center of display"
myPen up. "makes nib unable to draw"
myPen down. "enable the nib to draw"
myPen north. "points direction towards top"
myPen turn: -180. "add specified degrees to direction"
myPen direction. "get current angle of pen"
myPen go: 50. "move pen specified number of pixels"
myPen location. "get the pen position"
myPen goto: 200@200. "move to specified point"
myPen place: 250@250. "move to specified point without drawing"
myPen print: ''Hello World'' withFont: (TextStyle default fontAt: 1).
Display extent. "get display width@height"
Display width. "get display width"
Display height. "get display height"
].
'!
----- Method: TerseGuideHelp class>>point (in category 'pages') -----
point
^HelpTopic
title: 'Point'
contents:
'"************************************************************************
* Point: *
************************************************************************"
| x y |
x := 200@100. "obtain a new point"
y := x x. "x coordinate"
y := x y. "y coordinate"
x := 200@100 negated. "negates x and y"
x := (-200@ -100) abs. "absolute value of x and y"
x := (200.5(a)100.5) rounded. "round x and y"
x := (200.5(a)100.5) truncated. "truncate x and y"
x := 200@100 + 100. "add scale to both x and y"
x := 200@100 - 100. "subtract scale from both x and y"
x := 200@100 * 2. "multiply x and y by scale"
x := 200@100 / 2. "divide x and y by scale"
x := 200@100 // 2. "divide x and y by scale"
x := 200@100 \\ 3. "remainder of x and y by scale"
x := 200@100 + (50@25). "add points"
x := 200@100 - (50@25). "subtract points"
x := 200@100 * (3@4). "multiply points"
x := 200@100 // (3@4). "divide points"
x := 200@100 max: 50@200. "max x and y"
x := 200@100 min: 50@200. "min x and y"
x := 20@5 dotProduct: 10@2. "sum of product (x1*x2 + y1*y2)"
'!
----- Method: TerseGuideHelp class>>rectangle (in category 'pages') -----
rectangle
^HelpTopic
title: 'Rectangle'
contents:
'"************************************************************************
* Rectangle: *
************************************************************************"
Rectangle fromUser.
Rectangle origin: 0@0 corner: 100@100 "Origin and corners are absolute points"
Rectangle origin: 80@40 extent: 50@50 "Extent is added to origin"
Rectangle center: 40@50 extent: 30@20 "Center is half of extent"
Rectangle left: 1 right: 20 top: 1 bottom: 10
| col |
col := OrderedCollection new.
col add: (Rectangle center: 40@50 extent: 30@20).
col add: (Rectangle left: 1 right: 20 top: 1 bottom: 10).
Rectangle merging: col
'!
----- Method: TerseGuideHelp class>>set (in category 'pages') -----
set
^HelpTopic
title: 'Set'
contents:
'"************************************************************************
* Set: like Bag except duplicates not allowed *
* IdentitySet: uses identity test (== rather than =) *
************************************************************************"
| b x y sum max |
x := Set with: 4 with: 3 with: 2 with: 1. "create collection with up to 4 elements"
x := Set new. "allocate collection"
x add: 4; add: 3; add: 1; add: 2; yourself. "add element to collection"
y := x addAll: #(7 8 9). "add multiple elements to collection"
y := x removeAll: #(7 8 9). "remove multiple elements from collection"
y := x remove: 4 ifAbsent: []. "remove element from collection"
b := x isEmpty. "test if empty"
y := x size. "number of elements"
x includes: 4. "test if element is in collection"
x do: [:a | Transcript show: a printString; cr]. "iterate over the collection"
b := x allSatisfy: [:a | (a >= 1) & (a <= 4)]. "test if all elements meet condition"
y := x select: [:a | a > 2]. "return collection of elements that pass test"
y := x reject: [:a | a < 2]. "return collection of elements that fail test"
y := x collect: [:a | a + a]. "transform each element for new collection"
y := x detect: [:a | a > 3] ifNone: []. "return first element that passes test"
sum := 0. x do: [:a | sum := sum + a]. sum. "sum elements"
sum := x inject: 0 into: [:a :c | a + c]. "sum elements"
max := x inject: 0 into: [:a :c | (a > c) "find max element in collection"
ifTrue: [a]
ifFalse: [c]].
y := x asArray. "convert to array"
y := x asOrderedCollection. "convert to ordered collection"
y := x asSortedCollection. "convert to sorted collection"
y := x asBag. "convert to bag collection"
y := x asSet. "convert to set collection"
'!
----- Method: TerseGuideHelp class>>sortedCollection (in category 'pages') -----
sortedCollection
^HelpTopic
title: 'Sorted Collection'
contents:
'"************************************************************************
* SortedCollection: like OrderedCollection except order of elements *
* determined by sorting criteria *
************************************************************************"
| b x y sum max |
x := SortedCollection with: 4 with: 3 with: 2 with: 1. "create collection with up to 4 elements"
x := SortedCollection new. "allocate collection"
x := SortedCollection sortBlock: [:a :c | a > c]. "set sort criteria"
x add: 3; add: 2; add: 1; add: 4; yourself. "add element to collection"
"y := x addFirst: 5." "add element at beginning of collection"
y := x removeFirst. "remove first element in collection"
y := x addLast: 6. "add element at end of collection"
y := x removeLast. "remove last element in collection"
y := x addAll: #(7 8 9). "add multiple elements to collection"
y := x removeAll: #(7 8 9). "remove multiple elements from collection"
y := x remove: 5 ifAbsent: []. "remove element from collection"
b := x isEmpty. "test if empty"
y := x size. "number of elements"
y := x at: 2. "retrieve element at index"
y := x first. "retrieve first element in collection"
y := x last. "retrieve last element in collection"
b := x includes: 4. "test if element is in collection"
y := x copyFrom: 2 to: 3. "subcollection"
y := x indexOf: 3 ifAbsent: [0]. "first position of element within collection"
y := x occurrencesOf: 3. "number of times object in collection"
x do: [:a | Transcript show: a printString; cr]. "iterate over the collection"
b := x allSatisfy: [:a | (a >= 1) & (a <= 4)]. "test if all elements meet condition"
y := x select: [:a | a > 2]. "return collection of elements that pass test"
y := x reject: [:a | a < 2]. "return collection of elements that fail test"
y := x collect: [:a | a + a]. "transform each element for new collection"
y := x detect: [:a | a > 3] ifNone: []. "return first element that passes test"
y := x findFirst: [:a | a < 3]. "find position of first element that passes test"
sum := 0. x do: [:a | sum := sum + a]. sum. "sum elements"
sum := 0. 1 to: (x size) do: [:a | sum := sum + (x at: a)]. "sum elements"
sum := x inject: 0 into: [:a :c | a + c]. "sum elements"
max := x inject: 0 into: [:a :c | (a > c) "find max element in collection"
ifTrue: [a]
ifFalse: [c]].
y := x asArray. "convert to array"
y := x asOrderedCollection. "convert to ordered collection"
y := x asSortedCollection. "convert to sorted collection"
y := x asBag. "convert to bag collection"
y := x asSet. "convert to set collection"
'!
----- Method: TerseGuideHelp class>>string (in category 'pages') -----
string
^HelpTopic
title: 'String'
contents:
'"************************************************************************
* String: *
************************************************************************"
| b x y |
x := ''This is a string''. "string assignment"
x := ''String'', ''Concatenation''. "string concatenation"
b := x isEmpty. "test if string is empty"
y := x size. "string size"
y := x at: 2. "char at location"
y := x copyFrom: 2 to: 4. "substring"
y := x indexOf: $a ifAbsent: [0]. "first position of character within string"
x := String new: 4. "allocate string object"
x "set string elements"
at: 1 put: $a;
at: 2 put: $b;
at: 3 put: $c;
at: 4 put: $e.
x := String with: $a with: $b with: $c with: $d. "set up to 4 elements at a time"
x do: [:a | Transcript show: a printString; cr]. "iterate over the string"
b := x allSatisfy: [:a | (a >= $a) & (a <= $z)]. "test if all elements meet condition"
y := x select: [:a | a > $a]. "return all elements that meet condition"
y := x asSymbol. "convert string to symbol"
y := x asArray. "convert string to array"
x := ''ABCD'' asByteArray. "convert string to byte array"
y := x asOrderedCollection. "convert string to ordered collection"
y := x asSortedCollection. "convert string to sorted collection"
y := x asBag. "convert string to bag collection"
y := x asSet. "convert string to set collection"
y := x shuffled. "randomly shuffle string"
'!
----- Method: TerseGuideHelp class>>symbol (in category 'pages') -----
symbol
^HelpTopic
title: 'Symbol'
contents:
'"************************************************************************
* Symbol: *
************************************************************************"
| b x y |
x := #Hello. "symbol assignment"
y := ''String'', ''Concatenation''. "symbol concatenation (result is string)"
b := x isEmpty. "test if symbol is empty"
y := x size. "string size"
y := x at: 2. "char at location"
y := x copyFrom: 2 to: 4. "substring"
y := x indexOf: $e ifAbsent: [0]. "first position of character within string"
x do: [:a | Transcript show: a printString; cr]. "iterate over the string"
b := x allSatisfy: [:a | (a >= $a) & (a <= $z)]. "test if all elements meet condition"
y := x select: [:a | a > $a]. "return all elements that meet condition"
y := x asString. "convert symbol to string"
y := x asText. "convert symbol to text"
y := x asArray. "convert symbol to array"
y := x asOrderedCollection. "convert symbol to ordered collection"
y := x asSortedCollection. "convert symbol to sorted collection"
y := x asBag. "convert symbol to bag collection"
y := x asSet. "convert symbol to set collection"
'!
----- Method: TerseGuideHelp class>>time (in category 'pages') -----
time
^HelpTopic
title: 'Time'
contents:
'"************************************************************************
* Time: *
************************************************************************"
| x y b |
x := Time now. "create time from current time"
x := Time dateAndTimeNow. "create time from current time/date"
x := Time readFromString: ''3:47:26 pm''. "create time from formatted string"
x := Time fromSeconds: (60 * 60 * 4). "create time from elapsed time from midnight"
y := Time millisecondClockValue. "milliseconds since midnight"
y := Time totalSeconds. "total seconds since 1/1/1901"
y := x seconds. "seconds past minute (0-59)"
y := x minutes. "minutes past hour (0-59)"
y := x hours. "hours past midnight (0-23)"
y := x addTime: (Time now). "add time to time object"
y := x subtractTime: (Time now). "subtract time to time object"
y := x asSeconds. "convert time to seconds"
x := Time millisecondsToRun: [ "timing facility"
1 to: 1000 do: [:index | y := 3.14 * index]].
"b := (x <= Time now)." "comparison"
'!
----- Method: TerseGuideHelp class>>transcript (in category 'pages') -----
transcript
^HelpTopic
title: 'Transcript'
contents:
'"************************************************************************
* Transcript: *
************************************************************************"
Transcript clear. "clear to transcript window"
Transcript show: ''Hello World''. "output string in transcript window"
Transcript nextPutAll: ''Hello World''. "output string in transcript window"
Transcript nextPut: $A. "output character in transcript window"
Transcript space. "output space character in transcript window"
Transcript tab. "output tab character in transcript window"
Transcript cr. "carriage return / linefeed"
''Hello'' printOn: Transcript. "append print string into the window"
''Hello'' storeOn: Transcript. "append store string into the window"
Transcript endEntry. "flush the output buffer"
'!
Chris Muller uploaded a new version of SqueakSSL-Core to project Squeak 4.6:
http://source.squeak.org/squeak46/SqueakSSL-Core-ul.29.mcz
==================== Summary ====================
Name: SqueakSSL-Core-ul.29
Author: ul
Time: 16 October 2014, 10:36:36.902 am
UUID: e50d2a4f-cf16-4cdf-9ced-0d7471e550c6
Ancestors: SqueakSSL-Core-ul.28
Made #serverName: backwards compatible, by ignoring the primitive failure, when the plugin doesn't support it.
==================== Snapshot ====================
SystemOrganization addCategory: #'SqueakSSL-Core'!
SocketStream subclass: #SecureSocketStream
instanceVariableNames: 'ssl sendBuf readBuf decoded certIssues'
classVariableNames: ''
poolDictionaries: ''
category: 'SqueakSSL-Core'!
!SecureSocketStream commentStamp: 'ar 7/25/2010 14:19' prior: 0!
A variant on SocketStream supporting SSL/TLS encryption via SqueakSSL.
!
----- Method: SecureSocketStream>>ascii (in category 'accessing') -----
ascii
"Switch to ASCII"
super ascii.
decoded := (ReadStream
on: decoded originalContents asString
from: 1 to: decoded size)
position: decoded position;
yourself.
!
----- Method: SecureSocketStream>>atEnd (in category 'private-compat') -----
atEnd
"Pre Squeak 4.2 compatibility"
self receiveAvailableData.
^super atEnd!
----- Method: SecureSocketStream>>binary (in category 'accessing') -----
binary
"Switch to binary"
super binary.
decoded := (ReadStream
on: decoded originalContents asByteArray
from: 1 to: decoded size)
position: decoded position;
yourself.
!
----- Method: SecureSocketStream>>certError:code: (in category 'errors') -----
certError: errorString code: reason
"Signal an issue with a certificate. If the reason code matches the acceptable cert issues, continue, otherwise signal an error."
(certIssues allMask: reason) ifTrue:[^self].
^SqueakSSLCertificateError signal: errorString, '(code: ', reason, ')'.
!
----- Method: SecureSocketStream>>certState (in category 'accessing') -----
certState
"Returns the certificate verification bits. The returned value indicates
whether the certificate is valid. The two standard values are:
0 - The certificate is valid.
-1 - No certificate has been provided by the peer.
Otherwise, the result is a bit mask of the following values:
1 - If set, there is an unspecified issue with the cert (generic error)
2 - If set, the root CA is untrusted (usually a self-signed cert)
4 - If set, the certificate is expired.
8 - If set, the certificate is used for the wrong purpose
16 - If set, the CN of the certificate is invalid.
32 - If set, the certificate was revoked.
"
^ssl ifNotNil:[ssl certState]!
----- Method: SecureSocketStream>>close (in category 'initialize') -----
close
"Flush any data still not sent and take care of the socket."
super close.
ssl ifNotNil:[
ssl destroy.
ssl := nil.
].!
----- Method: SecureSocketStream>>destroy (in category 'initialize') -----
destroy
"Destroy the receiver and its underlying socket. Does not attempt to flush the output buffers. For a graceful close use SocketStream>>close instead."
"Pre-4.2 compatibility. Should be 'super destroy' instead of 'socket destroy'"
socket ifNotNil:[
socket destroy.
socket := nil.
].
ssl ifNotNil:[
ssl destroy.
ssl := nil.
].!
----- Method: SecureSocketStream>>flush (in category 'private-compat') -----
flush
"Pre-Squeak 4.2 compatibility"
((outNextToWrite > 1) and: [socket isOtherEndClosed not])
ifTrue: [
[self sendData: outBuffer count: outNextToWrite - 1]
on: ConnectionTimedOut
do: [:ex | shouldSignal ifFalse: ["swallow"]].
outNextToWrite := 1]
!
----- Method: SecureSocketStream>>ignoredCertIssues (in category 'accessing') -----
ignoredCertIssues
"Answer the mask of 'acceptable issues' with certs. To completely ignore all cert issues use -1 which still ensures privacy (encryption) to the remote host, but does not guard against a man-in-the-middle attack (i.e., you cannot be sure that the remote host is what he says he is). The reasons are a bit mask consisting of the following values:
1 - If set, there is an unspecified issue with the cert (generic error)
2 - If set, the root CA is untrusted (usually a self-signed cert)
4 - If set, the certificate is expired.
8 - If set, the certificate is used for the wrong purpose
16 - If set, the CN of the certificate is invalid.
32 - If set, the certificate was revoked.
"
^certIssues!
----- Method: SecureSocketStream>>ignoredCertIssues: (in category 'accessing') -----
ignoredCertIssues: reasonsMask
"Set the mask of 'acceptable issues' with certs. To completely ignore all cert issues use -1 which still ensures privacy (encryption) to the remote host, but does not guard against a man-in-the-middle attack (i.e., you cannot be sure that the remote host is what he says he is). The reasons are a bit mask consisting of the following values:
1 - If set, there is an unspecified issue with the cert (generic error)
2 - If set, the root CA is untrusted (usually a self-signed cert)
4 - If set, the certificate is expired.
8 - If set, the certificate is used for the wrong purpose
16 - If set, the CN of the certificate is invalid.
32 - If set, the certificate was revoked.
"
certIssues := reasonsMask!
----- Method: SecureSocketStream>>initialize (in category 'initialize') -----
initialize
"Initialize the receiver"
"I think 16k is the max for SSL frames so use a tad more"
decoded := ReadStream on: (ByteArray new: 20000) from: 1 to: 0.
super initialize.
sendBuf := ByteArray new: 4096.
readBuf := ByteArray new: 4096.
certIssues := 0.
!
----- Method: SecureSocketStream>>isDataAvailable (in category 'private-compat') -----
isDataAvailable
"Pre Squeak 4.2 compatibility"
self isInBufferEmpty ifFalse: [^true].
^self receiveAvailableData < inNextToWrite
!
----- Method: SecureSocketStream>>nextPutAllFlush: (in category 'private-compat') -----
nextPutAllFlush: aCollection
"Pre Squeak 4.2 compatibility"
| toPut |
toPut := binary ifTrue: [aCollection asByteArray] ifFalse: [aCollection asString].
self flush. "first flush pending stuff, then directly send"
socket isOtherEndClosed ifFalse: [
[self sendData: toPut count: toPut size]
on: ConnectionTimedOut
do: [:ex | shouldSignal ifFalse: ["swallow"]]]
!
----- Method: SecureSocketStream>>peerName (in category 'accessing') -----
peerName
"Returns the certificate name of the remote peer.
The method only returns a name if the certificate has been verified."
^ssl ifNotNil:[ssl peerName]!
----- Method: SecureSocketStream>>receiveAvailableData (in category 'private-compat') -----
receiveAvailableData
"Pre Squeak 4.2 compatibility"
recentlyRead := self receiveDataInto: inBuffer startingAt: inNextToWrite.
^self adjustInBuffer: recentlyRead
!
----- Method: SecureSocketStream>>receiveData (in category 'private-socket') -----
receiveData
"This method drains the available decryption data before waiting for the socket"
| pos |
"Note: The loop here is necessary to catch cases where a TLS packet is
split among TCP packets. In this case we would pull the first portion of
the TLS packet here but receiveAvailableData would return nothing since
the contents of the packet can't be decoded until the rest has come in."
[pos := inNextToWrite.
self receiveAvailableData.
pos = inNextToWrite ifFalse:[^pos].
"Pre-4.2 compatibility; should be 'super receiveData' instead."
socket
waitForDataFor: self timeout
ifClosed: [self shouldSignal
ifTrue:[ConnectionClosed signal: 'Connection closed while waiting for data.']]
ifTimedOut: [self shouldTimeout
ifTrue:[ConnectionTimedOut signal: 'Data receive timed out.']].
self isConnected] whileTrue.
"Final attempt to read data if a non-signaling connection closes"
^self receiveAvailableData.
!
----- Method: SecureSocketStream>>receiveData: (in category 'private-compat') -----
receiveData: nBytes
"Pre Squeak 4.2 compatibility"
self receiveAvailableData.
^super receiveData: nBytes.!
----- Method: SecureSocketStream>>receiveDataIfAvailable (in category 'private-compat') -----
receiveDataIfAvailable
"Pre Squeak 4.2 compatibility"
^self receiveAvailableData
!
----- Method: SecureSocketStream>>receiveDataInto:startingAt: (in category 'private-socket') -----
receiveDataInto: buffer startingAt: index
"Read and decrypt the data from the underlying socket. "
| count bytesRead |
"While in handshake, use the superclass version"
ssl ifNil:[
"Pre-4.2 compatibility; should be 'super receiveDataInto: buffer startingAt: index'"
^socket receiveAvailableDataInto: buffer startingAt: index.
].
"Only decode more data if all the decoded contents has been drained"
decoded atEnd ifTrue:[
"Decrypt more data if available"
bytesRead := 0.
[count := ssl decrypt: readBuf from: 1 to: bytesRead into: decoded originalContents.
count < 0 ifTrue:[^self error: 'SSL error, code: ', count].
bytesRead := 0.
count = 0 ifTrue:[
bytesRead := socket receiveAvailableDataInto: readBuf startingAt: 1.
].
bytesRead = 0] whileFalse.
"Update for number of bytes decoded"
decoded setFrom: 1 to: count.
].
"Push data from decoded into the result buffer"
count := (decoded size - decoded position) min: (buffer size - index + 1).
(decoded next: count into: buffer startingAt: index) size < count
ifTrue:[^self error: 'Unexpected read failure'].
^count
!
----- Method: SecureSocketStream>>sendData:count: (in category 'private-socket') -----
sendData: buffer count: n
"Encrypts the data before sending it on the underlying socket.
Breaks large chunks into 2k components to fit safely into ssl frame."
| remain start amount count |
"While in handshake, use the superclass version"
ssl ifNil:[
"Pre-4.2 compatibility; should be 'super sendData: buffer count: n' instead"
^socket sendData: buffer count: n
].
"Break the input into reasonable chunks and send them"
remain := n. start := 1.
[remain > 0] whileTrue:[
amount := remain min: 2048.
count := ssl encrypt: buffer from: start to: start+amount-1 into: sendBuf.
socket sendData: sendBuf count: count.
remain := remain - amount.
start := start + amount.
].!
----- Method: SecureSocketStream>>ssl (in category 'accessing') -----
ssl
"The SqueakSSL instance"
^ssl!
----- Method: SecureSocketStream>>sslAccept: (in category 'initialize') -----
sslAccept: certName
"Perform the SSL server handshake. This method uses all the common SocketStream methods to adhere to the various timeout/signalling settings of SocketStream. It only installs the SSL instance after the handshake is complete."
| squeakSSL result inbuf |
inbuf := ''.
squeakSSL := SqueakSSL new.
squeakSSL certName: certName.
"Perform the server handshake"
[[squeakSSL isConnected] whileFalse:[
"Read input"
self receiveData.
inbuf := self nextAvailable.
result := squeakSSL accept: inbuf from: 1 to: inbuf size into: sendBuf.
"Check for errors first"
result < -1 ifTrue:[^self error: 'SSL accept failed with code: ', result].
"If a token has been produced in the handshake, send it to the remote"
result > 0 ifTrue:[
self nextPutAll: (sendBuf copyFrom: 1 to: result).
self flush.
].
].
"There should be no pending data at this point, ensure it is so.
XXXX: If you ever see this problem, please inform me."
self isInBufferEmpty ifFalse:[self error: 'Unexpected input data'].
"We are connected. From here on, encryption will take place."
ssl := squeakSSL.
] ifCurtailed:[
"Make sure we destroy the platform handle if the handshake gets interrupted"
squeakSSL destroy.
].
!
----- Method: SecureSocketStream>>sslConnect (in category 'initialize') -----
sslConnect
"Perform the SSL client handshake. This method uses all the common SocketStream methods to adhere to the various timeout/signalling settings of SocketStream. It only installs the SSL instance after the handshake is complete."
self sslConnectTo: nil!
----- Method: SecureSocketStream>>sslConnectTo: (in category 'initialize') -----
sslConnectTo: serverName
"Perform the SSL client handshake. This method uses all the common SocketStream methods to adhere to the various timeout/signalling settings of SocketStream. It only installs the SSL instance after the handshake is complete. If serverName is not nil, then try to use it for SNI."
| inbuf squeakSSL result |
inbuf := ''.
squeakSSL := SqueakSSL new.
serverName ifNotNil: [ squeakSSL serverName: serverName ].
"Perform the SSL handshake"
[[result := squeakSSL connect: inbuf from: 1 to: inbuf size into: sendBuf.
result = 0] whileFalse:[
"Check for errors first"
result < -1 ifTrue:[^self error: 'SSL connect failed with code: ', result].
"If a token has been produced in the handshake, send it to the remote"
result > 0 ifTrue:[
self nextPutAll: (sendBuf copyFrom: 1 to: result).
self flush.
].
"Read more input and repeat"
self receiveData.
inbuf := self nextAvailable.
].
"There should be no pending data at this point, ensure it is so.
XXXX: If you ever see this problem, please inform me."
self isInBufferEmpty ifFalse:[self error: 'Unexpected input data'].
"We are connected. From here on, encryption will take place."
ssl := squeakSSL.
] ifCurtailed:[
"Make sure we destroy the platform handle if the handshake gets interrupted"
squeakSSL destroy.
].
!
----- Method: SecureSocketStream>>upToAll: (in category 'private-compat') -----
upToAll: aStringOrByteArray
"Pre Squeak 4.2 compatibility"
^self upToAll: aStringOrByteArray limit: 100000!
----- Method: SecureSocketStream>>upToAll:limit: (in category 'private-compat') -----
upToAll: aStringOrByteArray limit: nBytes
"Pre Squeak 4.2 compatibility"
| index sz result searchedSoFar target |
"Deal with ascii vs. binary"
self isBinary
ifTrue:[target := aStringOrByteArray asByteArray]
ifFalse:[target := aStringOrByteArray asString].
sz := target size.
"Look in the current inBuffer first"
index := inBuffer indexOfSubCollection: target
startingAt: lastRead - sz + 2.
(index > 0 and: [(index + sz) <= inNextToWrite]) ifTrue: ["found it"
result := self nextInBuffer: index - lastRead - 1.
self skip: sz.
^ result
].
[searchedSoFar := self inBufferSize.
"Receive more data"
self receiveData.
recentlyRead > 0] whileTrue:[
"Data begins at lastRead + 1, we add searchedSoFar as offset and
backs up sz - 1 so that we can catch any borderline hits."
index := inBuffer indexOfSubCollection: target
startingAt: (lastRead + searchedSoFar - sz + 2 max: 1).
(index > 0 and: [(index + sz) <= inNextToWrite]) ifTrue: ["found it"
result := self nextInBuffer: index - lastRead - 1.
self skip: sz.
^ result
].
"Check if we've exceeded the max. amount"
(nBytes notNil and:[inNextToWrite - lastRead > nBytes])
ifTrue:[^self nextAllInBuffer].
].
"not found and (non-signaling) connection was closed"
^self nextAllInBuffer!
----- Method: SecureSocketStream>>verifyCert: (in category 'initialize') -----
verifyCert: hostName
"Verifies the cert state and host name"
| certFlags |
certFlags := self certState.
certFlags = -1
ifTrue:[^self certError: 'No certificate was provided' code: -1].
certFlags = 0
ifFalse:[self certError: 'Invalid certificate' code: certFlags].
(ssl peerName match: hostName)
ifFalse:[self certError: 'Host name mismatch' code: -1].!
Socket subclass: #SecureSocket
instanceVariableNames: 'ssl decoded readBuf sendBuf'
classVariableNames: ''
poolDictionaries: ''
category: 'SqueakSSL-Core'!
----- Method: SecureSocket class>>google: (in category 'examples') -----
google: query
"An example HTTPS query to encrypted.google.com.
Example:
SecureSocket google: 'squeak'.
SecureSocket google: 'SqueakSSL'.
"
| hostName address socket |
"Change the host name to try an https request to some other host"
hostName := 'encrypted.google.com'..
address := NetNameResolver addressForName: hostName.
socket := SecureSocket newTCP.
"Connect the TCP socket"
socket connectTo: address port: 443.
socket waitForConnectionFor: 10.
["Handle the client handshake"
socket sslConnectTo: hostName.
"Verify that the cert is valid"
socket certState = 0 ifFalse:[
self error: 'The certificate is invalid (code: ', socket certState,')'.
].
"If the certificate is valid, make sure we're were we wanted to go"
(socket peerName match: hostName) ifFalse:[
self error: 'Host name mismatch: ', socket peerName.
].
"Send encrypted data"
socket sendData:
'GET /search?q=', query,' HTTP/1.0', String crlf,
'Host: ', hostName, String crlf,
'Connection: close', String crlf,
String crlf.
"Wait for the response"
^String streamContents:[:s|
[[true] whileTrue:[s nextPutAll: socket receiveData]]
on: ConnectionClosed, ConnectionTimedOut do:[:ex| ex return].
]] ensure:[socket destroy].
!
----- Method: SecureSocket>>accept (in category 'connect') -----
accept
"Accept a connection from the receiver socket.
Return a new socket that is connected to the client"
^self class acceptFrom: self.!
----- Method: SecureSocket>>certState (in category 'accessing') -----
certState
^ssl ifNotNil:[ssl certState]!
----- Method: SecureSocket>>decodeData (in category 'primitives') -----
decodeData
"Receive data from the given socket into the given array starting at the given index. Return the number of bytes read or zero if no data is available."
| total bytesRead |
decoded atEnd ifFalse:[^self].
"Decrypt more data if available"
bytesRead := 0.
[total := ssl decrypt: readBuf from: 1 to: bytesRead into: decoded originalContents.
total < 0 ifTrue:[^self error: 'SSL error, code: ', total].
bytesRead := 0.
total = 0 ifTrue:[
bytesRead := super primSocket: socketHandle receiveDataInto: readBuf startingAt: 1 count: readBuf size.
].
bytesRead = 0] whileFalse.
"Update for number of bytes decoded"
decoded setFrom: 1 to: total.
!
----- Method: SecureSocket>>destroy (in category 'initialize') -----
destroy
ssl ifNotNil:[
ssl destroy.
ssl := nil
].
super destroy.!
----- Method: SecureSocket>>initialize (in category 'initialize') -----
initialize
super initialize.
decoded := ReadStream on: (ByteArray new: 20000) from: 1 to: 0.
sendBuf := ByteArray new: 4096.
readBuf := ByteArray new: 4096.
!
----- Method: SecureSocket>>isConnected (in category 'primitives') -----
isConnected
"Return true if this socket is connected."
"We mustn't return false if there is data available"
^super isConnected or:[self dataAvailable]!
----- Method: SecureSocket>>peerName (in category 'accessing') -----
peerName
^ssl ifNotNil:[ssl peerName]!
----- Method: SecureSocket>>primSocket:receiveDataInto:startingAt:count: (in category 'primitives') -----
primSocket: socketID receiveDataInto: buffer startingAt: index count: count
"Receive data from the given socket into the given array starting at the given index. Return the number of bytes read or zero if no data is available."
| total |
ssl ifNil:[^super primSocket: socketID receiveDataInto: buffer startingAt: index count: count].
self decodeData.
"Push data from decoded into the result buffer"
total := (decoded size - decoded position) min: (buffer size - index + 1).
(decoded readInto: buffer startingAt: index count: total) = total
ifFalse:[self error: 'Unexpected read failure'].
^total
!
----- Method: SecureSocket>>primSocket:sendData:startIndex:count: (in category 'primitives') -----
primSocket: socketID sendData: buffer startIndex: start count: amount
"Send data to the remote host through the given socket starting with the given byte index of the given byte array. The data sent is 'pushed' immediately. Return the number of bytes of data actually sent; any remaining data should be re-submitted for sending after the current send operation has completed."
"Note: In general, it many take several sendData calls to transmit a large data array since the data is sent in send-buffer-sized chunks. The size of the send buffer is determined when the socket is created."
| count |
ssl ifNil:[^super primSocket: socketID sendData: buffer startIndex: start count: amount].
count := ssl encrypt: buffer from: start to: start+amount-1 into: sendBuf.
count < 0 ifTrue:[self error: 'SSL Error: ', count].
^super primSocket: socketID sendData: sendBuf startIndex: 1 count: count!
----- Method: SecureSocket>>primSocketReceiveDataAvailable: (in category 'primitives') -----
primSocketReceiveDataAvailable: socketID
"Return true if data may be available for reading from the current socket."
ssl ifNil:[^super primSocketReceiveDataAvailable: socketID].
self decodeData.
^decoded atEnd not!
----- Method: SecureSocket>>ssl (in category 'accessing') -----
ssl
"Answer the SqueakSSL instance"
^ssl!
----- Method: SecureSocket>>sslAccept: (in category 'connect') -----
sslAccept: certName
"Perform the SSL server handshake. This method uses all the common SocketStream methods to adhere to the various timeout/signalling settings of SocketStream. It only installs the SSL instance after the handshake is complete."
| squeakSSL result inbuf |
inbuf := ''.
squeakSSL := SqueakSSL new.
squeakSSL certName: certName.
"Perform the server handshake"
[[squeakSSL isConnected] whileFalse:[
"Read input"
inbuf := self receiveData.
result := squeakSSL accept: inbuf from: 1 to: inbuf size into: sendBuf.
"Check for errors first"
result < -1 ifTrue:[^self error: 'SSL accept failed with code: ', result].
"If a token has been produced in the handshake, send it to the remote"
result > 0 ifTrue:[self sendData: (sendBuf copyFrom: 1 to: result)].
].
"We are connected. From here on, encryption will take place."
ssl := squeakSSL.
] ifCurtailed:[
"Make sure we destroy the platform handle if the handshake gets interrupted"
squeakSSL destroy.
].
!
----- Method: SecureSocket>>sslConnect (in category 'connect') -----
sslConnect
"Perform the SSL client handshake. This method uses all the common SocketStream methods to adhere to the various timeout/signalling settings of SocketStream. It only installs the SSL instance after the handshake is complete."
self sslConnectTo: nil!
----- Method: SecureSocket>>sslConnectTo: (in category 'connect') -----
sslConnectTo: serverName
"Perform the SSL client handshake. This method uses all the common SocketStream methods to adhere to the various timeout/signalling settings of SocketStream. It only installs the SSL instance after the handshake is complete. If serverName is not nil, then try to use it for SNI."
| inbuf squeakSSL result |
inbuf := ''.
squeakSSL := SqueakSSL new.
serverName ifNotNil: [ squeakSSL serverName: serverName ].
"Perform the SSL handshake"
[[result := squeakSSL connect: inbuf from: 1 to: inbuf size into: sendBuf.
result = 0] whileFalse:[
"Check for errors first"
result < -1 ifTrue:[^self error: 'SSL connect failed with code: ', result].
"If a token has been produced in the handshake, send it to the remote"
result > 0 ifTrue:[self sendData: (sendBuf copyFrom: 1 to: result)].
"Read more input and repeat"
inbuf := self receiveData.
].
"We are connected. From here on, encryption will take place."
ssl := squeakSSL.
] ifCurtailed:[
"Make sure we destroy the platform handle if the handshake gets interrupted"
squeakSSL destroy.
].
!
Error subclass: #SqueakSSLCertificateError
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
category: 'SqueakSSL-Core'!
----- Method: SqueakSSLCertificateError>>isResumable (in category 'testing') -----
isResumable
"Determine whether an exception is resumable."
^true!
Object subclass: #SqueakSSL
instanceVariableNames: 'handle readBlock writeBlock'
classVariableNames: ''
poolDictionaries: ''
category: 'SqueakSSL-Core'!
!SqueakSSL commentStamp: 'ar 7/16/2010 23:14' prior: 0!
SqueakSSL provides an interface to the platforms SSL/TLS facilities.
!
----- Method: SqueakSSL class>>checkCert: (in category 'utilities') -----
checkCert: certName
"Attempt to verify the cert with the given name by performing
an SSL handshake. Raises an error if there is an issue with the cert,
returns the peer name from the cert if successful."
| sslClient sslServer inbuf outbuf result |
inbuf := ByteArray new: 4096.
outbuf := ByteArray new: 4096.
["Perform the SSL handshake"
sslClient := SqueakSSL new.
sslServer := SqueakSSL new.
sslServer certName: certName.
result := 0.
[result := sslClient connect: inbuf from: 1 to: result into: outbuf.
result = 0] whileFalse:[
result < -1 ifTrue:[^self error: 'SSL handshake failed (client code: ', result, ')'].
result := sslServer accept: outbuf from: 1 to: result into: inbuf.
result < -1 ifTrue:[^self error: 'SSL handshake failed (server code: ', result, ')'].
].
"Handshake complete. Check the cert status"
sslClient certState = 0 ifFalse:[
^self error: 'Certificate validation failed (code: ', sslClient certState, ')'.
].
"When successful, just return the peer name to the caller"
^sslClient peerName
] ensure:[
sslClient ifNotNil:[sslClient destroy].
sslServer ifNotNil:[sslServer destroy].
].!
----- Method: SqueakSSL class>>ensureSampleCert (in category 'examples') -----
ensureSampleCert
"Ensure that we have a sample certificate for the tests"
SqueakSSL platformName caseOf: {
['unix'] -> [^self ensureSampleCertFile].
['Win32'] -> [^self ensureSampleCertInStore].
} otherwise: [^nil].
!
----- Method: SqueakSSL class>>ensureSampleCertFile (in category 'examples') -----
ensureSampleCertFile
"On Unix, we can simply create a valid cert file"
| certName file |
SqueakSSL platformName = 'unix' ifFalse:[^self].
certName := self name, 'Cert.pem'.
(FileDirectory default fileExists: certName) ifFalse:[
file := FileDirectory default newFileNamed: certName.
[file nextPutAll: self exampleCertFile withUnixLineEndings] ensure:[file close].
].
^FileDirectory default fullNameFor: certName.
!
----- Method: SqueakSSL class>>ensureSampleCertInStore (in category 'examples') -----
ensureSampleCertInStore
"Ensure that we have a valid certificate in the Windows certificate store"
SqueakSSL platformName = 'Win32' ifFalse:[^self].
"Undocumented. Allows importing a pfx w/o password.
For the sole purpose of being able to run tests reliably"
SqueakSSL new setStringProperty: 10001 to: self exampleCertPFX.
^'testcert'. "Friendly name of test cert"
!
----- Method: SqueakSSL class>>exampleCertFile (in category 'examples') -----
exampleCertFile
^'-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQDnCv/gxDCb2yq15qkNwYtdMOHfW609Ck7wfwjVgzSNg+Hw+1R4
+krWhYRsWoXZUcy9xPC9WhnFCFijcnROcWp7vByVukFkVPYgzk1OBFT484ZCLBme
08GqLSzZrjgu7c1Yu5M9MZQdZKObBvZzDFsnvFccfM7G5mX/FgATasYaLQIDAQAB
AoGBAMpUJ6B+LtNOKykAxir1w0Xo+OTRM/SwglC57tKMBAmp5MNUVbVb+w3B/yWk
YHLf35yQSwKHVOnnVThNkuzfBY+MBxnaZwCByKknB4viP1ihPmfwdtqW4QXt1CTH
53sc9BVPjs3Nn1eEVrc582RK0MhORmjvlz+GkTswXCiKD3tBAkEA+6/au8T8XUeM
y/KrtJ+U84seviw5nY93Yg7495n4ir1fojp4wFbWq1JTeM22zspZQOKzEsjxfHUi
UH3buH//OwJBAOsAlJdIZqTIJponBXho+jqLHqcZYXBz3znDzHZU1PLfyfq2DuVe
gt8UWa4VwlCZNtPi7g/iFPEcLOlf2XY3hbcCQFU7voVsNlKYknPW4JMwn87CREz+
yRw0o6dPjry7JdJGQ4a66n2oatZl8OKuN8Rb/lHc8+vepPkS6eX8WVZn8lUCQE2r
F3EYgLQdYoS4ONqe93S53hukC8w6v6A70iuZxfevdvXhjfLI1cAc3bbngh1ZRgGp
kry1H+7APSe0gg7MMukCQQD3jdsVoc4yhziMdpUMyw6R6vYCMJbMEr/tI6CJYBG4
lW+zdcLK2d6GNpZU80F49HOvxH4HMg1Qv+UUiuxT7jpG
-----END RSA PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
MIICxTCCAi6gAwIBAgIJAN/0HUpkM5dvMA0GCSqGSIb3DQEBBQUAMEwxCzAJBgNV
BAYTAkdCMRIwEAYDVQQIEwlCZXJrc2hpcmUxEDAOBgNVBAcTB05ld2J1cnkxFzAV
BgNVBAoTDk15IENvbXBhbnkgTHRkMB4XDTExMDYwNjE0MzcyMFoXDTEyMDYwNTE0
MzcyMFowTDELMAkGA1UEBhMCR0IxEjAQBgNVBAgTCUJlcmtzaGlyZTEQMA4GA1UE
BxMHTmV3YnVyeTEXMBUGA1UEChMOTXkgQ29tcGFueSBMdGQwgZ8wDQYJKoZIhvcN
AQEBBQADgY0AMIGJAoGBAOcK/+DEMJvbKrXmqQ3Bi10w4d9brT0KTvB/CNWDNI2D
4fD7VHj6StaFhGxahdlRzL3E8L1aGcUIWKNydE5xanu8HJW6QWRU9iDOTU4EVPjz
hkIsGZ7TwaotLNmuOC7tzVi7kz0xlB1ko5sG9nMMWye8Vxx8zsbmZf8WABNqxhot
AgMBAAGjga4wgaswHQYDVR0OBBYEFGFwXmx2B6FB25yKMBm6g884lB2xMHwGA1Ud
IwR1MHOAFGFwXmx2B6FB25yKMBm6g884lB2xoVCkTjBMMQswCQYDVQQGEwJHQjES
MBAGA1UECBMJQmVya3NoaXJlMRAwDgYDVQQHEwdOZXdidXJ5MRcwFQYDVQQKEw5N
eSBDb21wYW55IEx0ZIIJAN/0HUpkM5dvMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcN
AQEFBQADgYEAbjMF7YzNQGovKD4NRjsnnKzQnUCTw6UquY2Oz/5SeLcPfLm8DudF
qppAjJjNpAgYC0yWoWcIxatYF/AsgGc2WL3hzI8oK7by6STfVi5RfLA6jS7lIDOv
4BUVsWZKADbEPsfiwed9b9MLLx8gpLLBrrr2rZpSyeDu4v16haV6wg8=
-----END CERTIFICATE-----
'!
----- Method: SqueakSSL class>>exampleCertPFX (in category 'examples') -----
exampleCertPFX
^ #[48 130 7 50 2 1 3 48 130 6 248 6 9 42 134 72 134 247 13 1 7 1 160 130 6 233 4 130 6 229 48 130 6 225 48 130 3 191 6 9 42 134 72 134 247 13 1 7 6 160 130 3 176 48 130 3 172 2 1 0 48 130 3 165 6 9 42 134 72 134 247 13 1 7 1 48 28 6 10 42 134 72 134 247 13 1 12 1 6 48 14 4 8 88 63 142 234 51 170 181 1 2 2 8 0 128 130 3 120 247 113 35 203 188 93 48 77 162 13 174 138 246 211 61 198 135 133 35 173 48 145 17 17 215 165 194 254 211 158 248 98 76 208 35 117 179 66 160 245 118 213 71 174 220 87 29 165 94 87 52 172 173 229 251 165 205 43 242 114 250 65 123 9 113 132 130 241 182 211 44 155 163 177 90 52 4 72 47 37 0 101 149 229 33 113 144 29 160 38 44 28 178 1 193 134 122 194 233 165 233 236 242 121 119 47 72 143 91 146 148 29 155 94 202 17 124 77 21 110 194 197 228 149 28 9 129 74 139 76 1 180 245 235 1 191 177 175 158 159 16 12 52 96 80 243 34 26 155 45 210 192 183 217 230 122 13 19 197 214 172 29 151 24 153 136 8 203 72 220 199 79 22 79 251 248 83 204 246 117 242 216 219 53 20 182 121 148 173 221 177 210 171 107 56 101 159 63 110 23 37 168 47 25 252 163 244 206 125 220 122 108 251 223 93 219 129 242 137 229 199 216 254 230 235 62 33 236 39 211 255 184 37 134 152 51 188 182 195 242 18 43 29 134 16 183 48 35 0 100 231 121 145 91 99 171 183 225 246 126 56 190 198 188 79 227 107 211 1 65 113 64 71 9 120 185 75 138 171 220 155 182 35 226 180 121 108 83 253 1 232 183 151 97 160 73 117 218 140 182 224 58 227 40 171 59 143 213 187 41 57 174 185 115 190 81 111 110 81 149 122 114 170 14 10 168 113 248 120 13 247 231 160 162 14 4 227 41 48 249 153 2 107 130 176 16 144 160 116 41 25 241 225 126 110 24 7 69 221 205 108 141 73 164 61 76 219 248 94 142 69 171 109 44 45 75 34 179 205 40 62 161 191 222 79 131 239 230 86 201 124 48 226 212 13 178 187 248 29 191 81 98 229 199 91 204 153 220 112 227 71 116 233 131 134 160 244 78 77 84 128 144 63 123 210 148 221 133 201 44 41 218 89 64 253 172 106 220 127 130 151 11 88 155 57 172 192 196 165 93 177 197 139 128 45 223 88 64 196 6 15 153 160 156 168 3 202 102 129 134 25 75 61 51 190 216 218 178 101 250 91 255 169 245 170 55 228 47 111 197 10 145 196 180 96 217 97 49 104 134 62 228 86 203 242 207 75 246 77 115 20 81 40 173 107 113 251 9 172 18 21 10 102 117 86 63 252 91 190 64 190 140 1 146 70 75 130 110 94 129 107 155 24 253 117 204 162 32 30 102 75 62 42 204 19 159 205 62 23 26 192 23 79 128 205 18 72 198 84 83 107 16 234 121 61 33 101 48 72 32 197 119 216 2 24 213 8 133 63 181 65 15 192 138 240 203 219 69 207 68 66 233 168 195 13 212 235 34 22 142 226 141 25 131 250 123 202 13 163 142 214 170 179 240 5 21 201 143 103 4 70 139 84 104 115 140 248 163 15 71 220 197 222 251 170 15 158 82 26 214 186 154 139 37 245 77 174 37 29 218 103 99 14 230 36 75 72 140 186 89 146 99 10 10 94 68 150 159 234 64 234 32 254 117 187 160 102 46 25 25 77 184 134 151 2 236 109 63 58 186 148 239 251 122 59 123 200 29 42 70 51 118 54 71 184 71 0 111 178 10 81 141 247 59 254 67 191 214 239 78 238 217 142 184 87 107 111 14 102 97 61 229 94 118 187 52 204 25 52 233 177 250 17 62 113 22 163 2 250 13 5 238 103 80 143 201 25 73 33 93 212 81 126 207 29 138 72 191 60 182 132 255 76 97 254 188 96 81 72 73 43 118 191 106 118 41 112 45 96 255 148 59 79 111 89 61 199 106 75 199 154 21 60 25 124 156 168 42 233 7 102 203 120 161 126 125 118 110 114 229 174 26 31 215 140 120 85 171 146 207 176 159 100 102 215 83 142 39 61 255 84 12 19 235 207 44 199 229 220 98 38 167 113 24 88 66 31 115 135 184 70 133 129 3 57 44 202 230 225 37 70 222 228 126 130 216 185 247 48 130 3 26 6 9 42 134 72 134 247 13 1 7 1 160 130 3 11 4 130 3 7 48 130 3 3 48 130 2 255 6 11 42 134 72 134 247 13 1 12 10 1 2 160 130 2 166 48 130 2 162 48 28 6 10 42 134 72 134 247 13 1 12 1 3 48 14 4 8 157 82 4 247 110 231 147 241 2 2 8 0 4 130 2 128 81 141 63 61 170 27 13 87 195 101 166 17 185 109 40 123 79 40 85 18 112 106 87 142 32 19 113 12 131 155 36 149 204 92 237 1 142 195 36 34 134 117 241 52 38 4 223 121 9 207 149 114 168 232 16 31 38 128 191 205 129 96 20 210 13 246 170 175 72 206 132 163 135 42 227 200 61 4 223 65 246 136 48 139 206 95 243 12 78 111 152 17 172 160 235 19 185 107 248 215 171 69 17 108 110 12 143 48 163 35 112 60 104 210 180 61 97 35 132 190 185 52 214 94 137 51 90 103 115 176 108 81 179 254 43 128 230 0 178 229 102 142 136 122 52 213 218 150 93 29 251 227 151 124 220 211 152 14 214 57 253 134 5 216 20 70 142 9 67 253 187 20 45 239 144 60 149 38 118 94 5 240 92 240 11 163 131 39 237 219 228 68 198 176 184 23 155 181 19 149 188 2 73 215 118 95 52 169 186 179 142 106 201 222 98 38 7 72 12 167 242 23 217 58 8 48 98 75 203 68 202 230 50 109 112 231 34 77 8 212 132 34 53 120 195 211 170 209 138 45 25 22 249 200 39 170 102 104 35 23 165 199 0 180 149 231 66 55 227 101 212 227 111 140 202 218 21 211 142 227 95 228 34 59 29 23 212 43 142 132 36 100 19 58 38 124 136 77 192 186 174 111 82 162 61 13 207 31 123 138 16 236 169 94 182 156 137 71 11 3 223 81 146 185 230 164 108 87 82 126 167 121 216 202 201 21 197 50 204 62 46 30 80 245 60 157 124 81 50 79 225 144 130 55 141 182 176 61 62 128 88 105 3 206 168 97 81 180 145 20 211 135 252 195 71 185 42 209 139 98 27 47 3 181 252 89 41 67 246 238 34 71 224 211 65 165 130 115 138 102 130 153 126 248 225 200 42 33 247 34 83 47 161 223 179 49 244 240 108 184 244 229 129 42 34 208 77 62 142 125 57 121 39 2 223 123 75 83 35 184 136 71 228 58 15 61 16 21 111 21 72 84 107 99 66 51 251 47 132 92 62 85 53 197 90 170 118 254 28 232 170 69 119 55 25 30 210 189 113 231 121 214 151 141 218 11 54 90 17 40 94 143 41 72 221 16 204 7 126 200 220 28 157 75 159 142 181 56 44 244 2 206 93 230 121 110 124 181 108 157 161 2 131 121 119 22 99 4 194 228 137 124 193 89 196 239 216 79 206 88 233 84 70 205 120 107 79 1 95 117 198 73 112 207 18 52 174 188 81 59 75 238 227 184 57 166 66 12 188 200 97 251 40 146 239 27 44 6 104 216 90 153 8 161 189 194 32 200 124 180 43 124 169 200 80 238 28 234 114 46 216 243 192 75 180 149 181 215 39 214 64 69 183 205 159 252 238 50 141 132 214 2 245 5 251 219 32 217 37 146 78 226 201 81 209 79 74 174 108 65 49 70 48 31 6 9 42 134 72 134 247 13 1 9 20 49 18 30 16 0 116 0 101 0 115 0 116 0 99 0 101 0 114 0 116 48 35 6 9 42 134 72 134 247 13 1 9 21 49 22 4 20 161 19 18 59 76 168 198 72 97 179 205 74 244 65 111 116 223 140 145 154 48 49 48 33 48 9 6 5 43 14 3 2 26 5 0 4 20 182 216 177 70 221 73 183 142 238 169 97 22 175 148 97 145 207 223 75 54 4 8 178 120 42 60 194 226 96 245 2 2 8 0]!
----- Method: SqueakSSL class>>google: (in category 'examples') -----
google: query
"An example HTTPS query to encrypted.google.com.
Example:
SqueakSSL google: 'squeak'.
SqueakSSL google: 'SqueakSSL'.
"
| hostName address socket ssl |
"Change the host name to try an https request to some other host"
hostName := 'encrypted.google.com'..
address := NetNameResolver addressForName: hostName.
socket := Socket newTCP.
"Connect the TCP socket"
socket connectTo: address port: 443.
socket waitForConnectionFor: 10.
"Set up SqueakSSL using the convenience APIs"
ssl := SqueakSSL on: socket.
["Let SqueakSSL handle the client handshake"
ssl connect.
"Verify that the cert is valid"
ssl certState = 0 ifFalse:[
self error: 'The certificate is invalid (code: ', ssl certState,')'.
].
"If the certificate is valid, make sure we're were we wanted to go"
(ssl peerName match: hostName) ifFalse:[
self error: 'Host name mismatch: ', ssl peerName.
].
"Send encrypted data"
ssl sendData:
'GET /search?q=', query,' HTTP/1.0', String crlf,
'Host: ', hostName, String crlf,
'Connection: close', String crlf,
String crlf.
"Wait for the response"
^String streamContents:[:s|
[socket isConnected | socket dataAvailable]
whileTrue:[s nextPutAll: ssl receiveData]].
] ensure:[ssl destroy].
!
----- Method: SqueakSSL class>>on: (in category 'instance creation') -----
on: aSocket
"Convenience API. Create a SqueakSSL operating on a standard TCP socket.
Generally not very useful for real applications (it lacks error handling etc)
but very helpful for debugging and other experiments."
^self new on: aSocket!
----- Method: SqueakSSL class>>platformName (in category 'utilities') -----
platformName
"Return the name of the platform we're running on."
^Smalltalk getSystemAttribute: 1001!
----- Method: SqueakSSL class>>secureSocket (in category 'instance creation') -----
secureSocket
"Answer the class to use as secure socket implementation.
Provided here so that users only need a dependency on SqueakSSL."
^SecureSocket!
----- Method: SqueakSSL class>>secureSocketStream (in category 'instance creation') -----
secureSocketStream
"Answer the class to use as secure socket stream implementation.
Provided here so that users only need a dependency on SqueakSSL."
^SecureSocketStream!
----- Method: SqueakSSL class>>serverOn:certName: (in category 'examples') -----
serverOn: port certName: certName
"An HTTPS server example. Fires up a listener at the given port such that
you can point a browser to that https url. Responds with a single line of text
and closes the listener after the first connection.
SqueakSSL
serverOn: 8443
certName: 'Internet Widgits Pty'.
SqueakSSL
serverOn: 8443
certName: '/home/andreas/certs/testcert.pem'.
"
| listener socket ssl |
"Set up the listener socket"
listener := Socket newTCP.
listener listenOn: port backlogSize: 8.
[socket := listener waitForAcceptFor: 30.
socket == nil] whileTrue.
listener destroy.
"Set up SqueakSSL for the just accepted connection"
[ssl := SqueakSSL on: socket.
"The SSL needs the cert name."
ssl certName: certName.
"Let SqueakSSL do the server handshake"
ssl accept.
"Read out the HTTPS request"
ssl receiveData.
"And send the response"
ssl sendData:
'HTTP/1.0 200 OK', String crlf,
'Connection: close', String crlf,
'Content-Type: text/plain', String crlf,
'Server: SqueakSSL', String crlf,
String crlf,
'This is a successful SqueakSSL response.'.
socket close.
] ensure:[
ssl destroy.
socket destroy.
].!
----- Method: SqueakSSL>>accept (in category 'convenience') -----
accept
"Convenience API. Perform an SSL server handshake.
Raises an error if something goes wrong."
| inbuf outbuf count result |
inbuf := ByteArray new: 4096.
outbuf := ByteArray new: 4096.
count := 0.
[self isConnected] whileFalse:[
"Read input"
count := self readDataInto: inbuf.
result := self accept: inbuf from: 1 to: count into: outbuf.
"Check for errors first"
result < -1 ifTrue:[^self error: 'SSL accept failed with code: ', result].
"If a token has been produced in the handshake, send it to the remote"
result > 0 ifTrue:[self writeData: outbuf count: result].
].
!
----- Method: SqueakSSL>>accept:from:to:into: (in category 'operations') -----
accept: srcBuf from: start to: stop into: dstBuf
"Start or continue the server handshake using the given input token."
^self primitiveSSL: handle accept: srcBuf startingAt: start count: stop-start+1 into: dstBuf!
----- Method: SqueakSSL>>certName (in category 'accessing') -----
certName
"The name of the (local) certificate to provide to the remote peer."
^self primitiveSSL: handle getStringProperty: 1!
----- Method: SqueakSSL>>certName: (in category 'accessing') -----
certName: aString
"Sets the name of the (local) certificate to provide to the remote peer.
OpenSSL:
The name is the full path to a .pem file.
WinSSL:
The name is matched against the 'friendly name' of a certificate in the cert store.
"
^self primitiveSSL: handle setStringProperty: 1 toValue: (aString ifNil:[''])!
----- Method: SqueakSSL>>certState (in category 'accessing') -----
certState
"Returns the certificate verification bits. The returned value indicates
whether the certificate is valid. The two standard values are:
0 - The certificate is valid.
-1 - No certificate has been provided by the peer.
Otherwise, the result is a bit mask of the following values:
1 - If set, there is an unspecified issue with the cert (generic error)
2 - If set, the root CA is untrusted (usually a self-signed cert)
4 - If set, the certificate is expired.
8 - If set, the certificate is used for the wrong purpose
16 - If set, the CN of the certificate is invalid.
32 - If set, the certificate was revoked.
"
^self primitiveSSL: handle getIntProperty: 3!
----- Method: SqueakSSL>>connect (in category 'convenience') -----
connect
"Convenience API. Perform an SSL client handshake.
Raises an error if something goes wrong."
| inbuf outbuf count result |
inbuf := ByteArray new: 4096.
outbuf := ByteArray new: 4096.
count := 0.
"Begin the SSL handshake"
[result := self connect: inbuf from: 1 to: count into: outbuf.
result = 0] whileFalse:[
"Check for errors first"
result < -1 ifTrue:[^self error: 'SSL connect failed with code: ', result].
"If a token has been produced in the handshake, send it to the remote"
result > 0 ifTrue:[self writeData: outbuf count: result].
"Read more input and repeat"
count := self readDataInto: inbuf.
].!
----- Method: SqueakSSL>>connect:from:to:into: (in category 'operations') -----
connect: srcBuf from: start to: stop into: dstBuf
"Start or continue the server handshake using the given input token."
^self primitiveSSL: handle connect: srcBuf startingAt: start count: stop-start+1 into: dstBuf!
----- Method: SqueakSSL>>decrypt: (in category 'convenience') -----
decrypt: data
"Convenience API. Decrypt incoming data and return the result.
Warning: This method may produce more or less results than expected
unless called with exactly one SSL/TLS frame."
| buf count |
buf := data class new: 4096.
count := self decrypt: data from: 1 to: data size into: buf.
count < 0 ifTrue:[self error: 'Decryption failed, code: ', count].
^buf copyFrom: 1 to: count!
----- Method: SqueakSSL>>decrypt:from:to:into: (in category 'operations') -----
decrypt: srcBuf from: start to: stop into: dstBuf
"Decrypt the input in srcBuf into the provided output buffer.
Clients are expected to adhere to the following rules:
* The size of dstBuf must be large enough for the largest encrypted packet.
* Clients must not call this method with a huge srcBuf (tens of kb of data)
* After having called this method with new input, clients must call it
with NO input until all data has been 'drained' for example:
count := squeakSSL decrypt: srcBuf into: dstBuf.
[count > 0] whileTrue:[
count := squeakSSL decrypt: #[] into: dstBuf.
].
"
^self primitiveSSL: handle decrypt: srcBuf startingAt: start count: stop-start+1 into: dstBuf!
----- Method: SqueakSSL>>destroy (in category 'initialize') -----
destroy
"Destroys the underlying platform handle"
handle ifNotNil:[
self primitiveSSLDestroy: handle.
handle := nil.
].!
----- Method: SqueakSSL>>encrypt: (in category 'convenience') -----
encrypt: data
"Convenience API. Encrypt incoming data and return the result."
| buf count |
buf := data class new: data size + 100.
count := self encrypt: data from: 1 to: data size into: buf.
count < 0 ifTrue:[self error: 'Decryption failed, code: ', count].
^buf copyFrom: 1 to: count!
----- Method: SqueakSSL>>encrypt:from:to:into: (in category 'operations') -----
encrypt: srcBuf from: start to: stop into: dstBuf
"Encrypt the input in srcBuf into the provided output buffer.
The output buffer must be large enough to include the framing information."
^self primitiveSSL: handle encrypt: srcBuf startingAt: start count: stop-start+1 into: dstBuf!
----- Method: SqueakSSL>>initialize (in category 'initialize') -----
initialize
"Initialize the receiver"
handle := self primitiveSSLCreate.
!
----- Method: SqueakSSL>>isConnected (in category 'testing') -----
isConnected
"Returns true if the SSL handshake has been completed"
^self sslState = 3!
----- Method: SqueakSSL>>logLevel (in category 'accessing') -----
logLevel
"Returns the log level of the ssl instance"
^self primitiveSSL: handle getIntProperty: 1!
----- Method: SqueakSSL>>logLevel: (in category 'accessing') -----
logLevel: aNumber
"Sets the log level of the ssl instance"
^self primitiveSSL: handle setIntProperty: 1 toValue: aNumber!
----- Method: SqueakSSL>>on: (in category 'initialize') -----
on: aSocket
"Convenience API. Set up SqueakSSL to operate on a standard TCP socket.
Generally not very useful for real applications (it lacks error handling etc)
but very helpful for debugging and other experiments."
self readBlock:[:inbuf|
aSocket waitForDataIfClosed:[].
aSocket receiveDataInto: inbuf.
].
self writeBlock:[:outbuf :count|
aSocket sendData: (outbuf copyFrom: 1 to: count).
].!
----- Method: SqueakSSL>>peerName (in category 'accessing') -----
peerName
"Returns the certificate name of the remote peer.
The method only returns a name if the certificate has been verified."
^self primitiveSSL: handle getStringProperty: 0!
----- Method: SqueakSSL>>pluginVersion (in category 'accessing') -----
pluginVersion
"Returns the version of the plugin"
^self primitiveSSL: handle getIntProperty: 0!
----- Method: SqueakSSL>>primitiveSSL:accept:startingAt:count:into: (in category 'primitives') -----
primitiveSSL: sslHandle accept: srcbuf startingAt: start count: length into: dstbuf
"Primitive. Starts or continues a server handshake using the provided data.
Will eventually produce output to be sent to the server.
Returns:
> 0 - Number of bytes to be sent to the server
0 - Success. The connection is established.
-1 - More input is required.
< -1 - Other errors
"
<primitive: 'primitiveAccept' module: 'SqueakSSL'>
^self primitiveFailed!
----- Method: SqueakSSL>>primitiveSSL:connect:startingAt:count:into: (in category 'primitives') -----
primitiveSSL: sslHandle connect: srcbuf startingAt: start count: length into: dstbuf
"Primitive. Starts or continues a client handshake using the provided data.
Will eventually produce output to be sent to the server.
Returns:
> 0 - Number of bytes to be sent to the server
0 - Success. The connection is established.
-1 - More input is required.
< -1 - Other errors
"
<primitive: 'primitiveConnect' module: 'SqueakSSL'>
^self primitiveFailed!
----- Method: SqueakSSL>>primitiveSSL:decrypt:startingAt:count:into: (in category 'primitives') -----
primitiveSSL: sslHandle decrypt: srcbuf startingAt: start count: length into: dstbuf
"Primitive. Takes incoming data for decryption and continues to decrypt data.
Returns the number of bytes produced in the output"
<primitive: 'primitiveDecrypt' module: 'SqueakSSL'>
^self primitiveFailed!
----- Method: SqueakSSL>>primitiveSSL:encrypt:startingAt:count:into: (in category 'primitives') -----
primitiveSSL: sslHandle encrypt: srcbuf startingAt: start count: length into: dstbuf
"Primitive. Encrypts the incoming buffer into the result buffer.
Returns the number of bytes produced as a result."
<primitive: 'primitiveEncrypt' module: 'SqueakSSL'>
^self primitiveFailed!
----- Method: SqueakSSL>>primitiveSSL:getIntProperty: (in category 'primitives') -----
primitiveSSL: sslHandle getIntProperty: propID
"Primitive. Returns a string property from an SSL session."
<primitive: 'primitiveGetIntProperty' module: 'SqueakSSL'>
^self primitiveFailed!
----- Method: SqueakSSL>>primitiveSSL:getStringProperty: (in category 'primitives') -----
primitiveSSL: sslHandle getStringProperty: propID
"Primitive. Returns a string property from an SSL session."
<primitive: 'primitiveGetStringProperty' module: 'SqueakSSL'>
^self primitiveFailed!
----- Method: SqueakSSL>>primitiveSSL:setIntProperty:toValue: (in category 'primitives') -----
primitiveSSL: sslHandle setIntProperty: propID toValue: anInteger
"Primitive. Sets a string property in an SSL session."
<primitive: 'primitiveSetIntProperty' module: 'SqueakSSL'>
^self primitiveFailed!
----- Method: SqueakSSL>>primitiveSSL:setStringProperty:toValue: (in category 'primitives') -----
primitiveSSL: sslHandle setStringProperty: propID toValue: aString
"Primitive. Sets a string property in an SSL session."
<primitive: 'primitiveSetStringProperty' module: 'SqueakSSL'>
^self primitiveFailed!
----- Method: SqueakSSL>>primitiveSSLCreate (in category 'primitives') -----
primitiveSSLCreate
"Primitive. Creates and returns a new SSL handle"
<primitive: 'primitiveCreate' module: 'SqueakSSL'>
^self primitiveFailed!
----- Method: SqueakSSL>>primitiveSSLDestroy: (in category 'primitives') -----
primitiveSSLDestroy: sslHandle
"Primitive. Destroys the SSL session handle"
<primitive: 'primitiveDestroy' module: 'SqueakSSL'>
^self primitiveFailed!
----- Method: SqueakSSL>>readBlock (in category 'accessing') -----
readBlock
"The block used to read data where required. The block takes one argument,
the buffer to fill with data and is expected to return the number of bytes read."
^readBlock!
----- Method: SqueakSSL>>readBlock: (in category 'accessing') -----
readBlock: aBlock
"The block used to read data where required. The block takes one argument,
the buffer to fill with data and is expected to return the number of bytes read."
readBlock := aBlock!
----- Method: SqueakSSL>>readDataInto: (in category 'private') -----
readDataInto: aBuffer
"Private. Read actual data into the given buffer.
Return the number of bytes read."
^readBlock value: aBuffer!
----- Method: SqueakSSL>>receiveData (in category 'convenience') -----
receiveData
"Convenience API. Receive data and decrypt it."
| inbuf outbuf count |
inbuf := String new: 4096.
outbuf := String new: 4096.
^String streamContents:[:s|
"Read the next input bytes"
count := self readDataInto: inbuf.
"Push the input bytes into the SSL"
count := self decrypt: inbuf from: 1 to: count into: outbuf.
"And keep draining as long as output is being produced"
[count > 0] whileTrue:[
s next: count putAll: outbuf.
count := self decrypt: inbuf from: 1 to: 0 into: outbuf.
].
].!
----- Method: SqueakSSL>>sendData: (in category 'convenience') -----
sendData: inbuf
"Convenience API. Encrypt and send data"
| outbuf count |
outbuf := inbuf class new: inbuf size + 100.
count := self encrypt: inbuf from: 1 to: inbuf size into: outbuf.
^self writeData: outbuf count: count.!
----- Method: SqueakSSL>>serverName: (in category 'accessing') -----
serverName: aString
"Sets the name to use with the Server Name Indication TLS extension. Which should be a valid FQDN. No WinSSL support yet."
^[ self primitiveSSL: handle setStringProperty: 2 toValue: aString ]
on: Error
do: [ "nothing" ]!
----- Method: SqueakSSL>>setStringProperty:to: (in category 'private') -----
setStringProperty: index to: aString
"Private. Use with caution"
^self primitiveSSL: handle setStringProperty: index toValue: aString!
----- Method: SqueakSSL>>sslState (in category 'accessing') -----
sslState
"Returns the current state of the SSL connection:
0 - Unused.
1 - In accept handshake.
2 - In connect handshake.
3 - Connected.
"
^self primitiveSSL: handle getIntProperty: 2
!
----- Method: SqueakSSL>>writeBlock (in category 'accessing') -----
writeBlock
"The block used to write data where required. The block takes two arguments,
the buffer and the number of bytes to be written from the buffer."
^writeBlock!
----- Method: SqueakSSL>>writeBlock: (in category 'accessing') -----
writeBlock: aBlock
"The block used to write data where required. The block takes two arguments,
the buffer and the number of bytes to be written from the buffer."
writeBlock := aBlock!
----- Method: SqueakSSL>>writeData:count: (in category 'private') -----
writeData: aBuffer count: count
"Private. Write actual data from the given buffer."
writeBlock value: aBuffer value: count!