Chris Muller uploaded a new version of HelpSystem-Core to project Squeak 4.6:
http://source.squeak.org/squeak46/HelpSystem-Core-mt.78.mcz ==================== Summary ==================== Name: HelpSystem-Core-mt.78 Author: mt Time: 14 May 2015, 6:45:57.427 pm UUID: d3d02275-f61e-8f4f-91e0-5dcf9b37c8d5 Ancestors: HelpSystem-Core-kfr.77 Class-based help topics are editable again. Help browser updates correctly after edits. ==================== Snapshot ==================== SystemOrganization addCategory: #'HelpSystem-Core-Builders'! SystemOrganization addCategory: #'HelpSystem-Core-Help'! SystemOrganization addCategory: #'HelpSystem-Core-Model'! SystemOrganization addCategory: #'HelpSystem-Core-UI'! SystemOrganization addCategory: #'HelpSystem-Core-Utilities'! (PackageInfo named: 'HelpSystem-Core') postscript: '"below, add code to be run after the loading of this package" TheWorldMainDockingBar updateInstances'! Model subclass: #AbstractHelpTopic instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'HelpSystem-Core-Model'! !AbstractHelpTopic commentStamp: 'mt 3/24/2015 16:26' prior: 0! A HelpTopic provides content information that can be used as a help to the user. It can be labeled with a title and marked with an (optional) icon. Help topics form a hierarchy since any topic is able to have zero or more subtopics. ! ----- Method: AbstractHelpTopic>><= (in category 'comparing') ----- <= anotherHelpTopic "Priority-based: ... -3 -2 -1 nil nil nil 1 2 3 4 ..." (self priority notNil and: [anotherHelpTopic priority notNil]) ifTrue: [^ self priority <= anotherHelpTopic priority]. (self priority notNil and: [anotherHelpTopic priority isNil]) ifTrue: [^ self priority <= 0]. (self priority isNil and: [anotherHelpTopic priority notNil]) ifTrue: [^ anotherHelpTopic priority >= 0]. "Fall-back." ^ self title <= anotherHelpTopic title! ----- Method: AbstractHelpTopic>>asHelpTopic (in category 'conversion') ----- asHelpTopic ^ self! ----- Method: AbstractHelpTopic>>contents (in category 'accessing') ----- contents "Return the text contents of this topic." self subclassResponsibility.! ----- Method: AbstractHelpTopic>>hasSubtopics (in category 'testing') ----- hasSubtopics ^ self subtopics notEmpty! ----- Method: AbstractHelpTopic>>icon (in category 'accessing') ----- icon "Returns a descriptive form to support manual detection in a list of topics. Icons may encode the kind of topic." ^ nil! ----- Method: AbstractHelpTopic>>isEditable (in category 'testing') ----- isEditable ^ false! ----- Method: AbstractHelpTopic>>isSearchable (in category 'testing') ----- isSearchable ^ true! ----- Method: AbstractHelpTopic>>printOn: (in category 'printing') ----- printOn: stream | title | super printOn: stream. (title := self title) notNil ifTrue: [stream nextPutAll: '<' , title , '>'].! ----- Method: AbstractHelpTopic>>priority (in category 'accessing') ----- priority ^ nil! ----- Method: AbstractHelpTopic>>priorityForSearch (in category 'accessing') ----- priorityForSearch ^ 0! ----- Method: AbstractHelpTopic>>refresh (in category 'updating') ----- refresh "Do nothing."! ----- Method: AbstractHelpTopic>>subtopics (in category 'accessing') ----- subtopics "Topics can be nested in a tree structure." ^ #()! ----- Method: AbstractHelpTopic>>title (in category 'accessing') ----- title "A brief description of this topic's contents." ^ self contents truncateWithElipsisTo: 20! AbstractHelpTopic subclass: #ClassAPIHelpTopic instanceVariableNames: 'theClass withSubclasses withMethods subclassesAsSeparateTopic' classVariableNames: '' poolDictionaries: '' category: 'HelpSystem-Core-Model'! !ClassAPIHelpTopic commentStamp: 'mt 3/25/2015 15:03' prior: 0! Show comments of a class, their subclasses and methods.! ----- Method: ClassAPIHelpTopic>>contents (in category 'accessing') ----- contents ^ self theClass instanceSide organization classComment! ----- Method: ClassAPIHelpTopic>>icon (in category 'accessing') ----- icon ^ ToolIcons iconNamed: (ToolIcons iconForClass: self theClass name)! ----- Method: ClassAPIHelpTopic>>subclassesAsSeparateTopic (in category 'accessing') ----- subclassesAsSeparateTopic ^ subclassesAsSeparateTopic! ----- Method: ClassAPIHelpTopic>>subclassesAsSeparateTopic: (in category 'accessing') ----- subclassesAsSeparateTopic: aBoolean subclassesAsSeparateTopic := aBoolean.! ----- Method: ClassAPIHelpTopic>>subtopics (in category 'accessing') ----- subtopics ^ (self withMethods ifFalse: [#()] ifTrue: [ { MethodListHelpTopic new theClass: self theClass theNonMetaClass. MethodListHelpTopic new theClass: self theClass theMetaClass }]), (self withSubclasses ifFalse: [#()] ifTrue: [ | topics | topics := self theClass subclasses collect: [:cls | self class new theClass: cls; subclassesAsSeparateTopic: self subclassesAsSeparateTopic; withMethods: self withMethods; withSubclasses: self withSubclasses]. self subclassesAsSeparateTopic ifTrue: [{(HelpTopic named: 'Subclasses') subtopics: topics; yourself}] ifFalse: [topics]])! ----- Method: ClassAPIHelpTopic>>theClass (in category 'accessing') ----- theClass ^ theClass! ----- Method: ClassAPIHelpTopic>>theClass: (in category 'accessing') ----- theClass: aClassDescription theClass := aClassDescription.! ----- Method: ClassAPIHelpTopic>>title (in category 'accessing') ----- title ^ self theClass name! ----- Method: ClassAPIHelpTopic>>withMethods (in category 'accessing') ----- withMethods ^ withMethods ifNil:[false]! ----- Method: ClassAPIHelpTopic>>withMethods: (in category 'accessing') ----- withMethods: aBoolean withMethods := aBoolean.! ----- Method: ClassAPIHelpTopic>>withSubclasses (in category 'accessing') ----- withSubclasses ^ withSubclasses ifNil:[false]! ----- Method: ClassAPIHelpTopic>>withSubclasses: (in category 'accessing') ----- withSubclasses: aBoolean withSubclasses := aBoolean.! AbstractHelpTopic subclass: #ClassBasedHelpTopic instanceVariableNames: 'helpClass subtopics' classVariableNames: '' poolDictionaries: '' category: 'HelpSystem-Core-Model'! !ClassBasedHelpTopic commentStamp: 'mt 3/24/2015 16:28' prior: 0! This kind of topic uses subclasses and methods to encode books and pages.! ----- Method: ClassBasedHelpTopic>>contents (in category 'accessing') ----- contents "A book has no contents. Only its pages do." ^ ''! ----- Method: ClassBasedHelpTopic>>hasSubtopics (in category 'testing') ----- hasSubtopics ^ self helpClass pages notEmpty or: [self helpClass subclasses notEmpty]! ----- Method: ClassBasedHelpTopic>>helpClass (in category 'accessing') ----- helpClass ^ helpClass! ----- Method: ClassBasedHelpTopic>>helpClass: (in category 'accessing') ----- helpClass: aHelpClass helpClass := aHelpClass.! ----- Method: ClassBasedHelpTopic>>icon (in category 'accessing') ----- icon ^ self helpClass icon! ----- Method: ClassBasedHelpTopic>>isEditable (in category 'testing') ----- isEditable ^ true! ----- Method: ClassBasedHelpTopic>>priority (in category 'accessing') ----- priority ^ self helpClass priority! ----- Method: ClassBasedHelpTopic>>refresh (in category 'updating') ----- refresh self updateSubtopics. self changed: #subtopicsUpdated.! ----- Method: ClassBasedHelpTopic>>subtopics (in category 'accessing') ----- subtopics ^ subtopics ifNil: [self updateSubtopics]! ----- Method: ClassBasedHelpTopic>>title (in category 'accessing') ----- title ^ self helpClass bookName! ----- Method: ClassBasedHelpTopic>>updateSubtopics (in category 'updating') ----- updateSubtopics | pages | pages := (self helpClass pages collect: [:pageSelectorOrClassName | (Smalltalk hasClassNamed: pageSelectorOrClassName asString) ifTrue: [Smalltalk classNamed: pageSelectorOrClassName asString] ifFalse: [pageSelectorOrClassName]]) asOrderedCollection. self helpClass subclasses select: [:cls | cls ignore not] thenDo: [:cls | pages addIfNotPresent: cls]. ^ subtopics := pages withIndexCollect: [:pageSelectorOrClass :priority | pageSelectorOrClass isBehavior ifFalse: [(self helpClass perform: pageSelectorOrClass) priority: priority - pages size; key: pageSelectorOrClass; yourself] ifTrue: [pageSelectorOrClass asHelpTopic]]! AbstractHelpTopic subclass: #HelpTopic instanceVariableNames: 'title key icon contents subtopics priority' classVariableNames: '' poolDictionaries: '' category: 'HelpSystem-Core-Model'! !HelpTopic commentStamp: 'mt 3/25/2015 11:27' prior: 0! This is a configurable version of a help topic. You can define its contents, title, icon, and subtopics manually. Help builders make use of this.! ----- Method: HelpTopic class>>named: (in category 'instance creation') ----- named: aString "Create a new instance with given title and empty contents" ^(self new) title: aString; yourself! ----- Method: HelpTopic class>>title:contents: (in category 'instance creation') ----- title: aTitle contents: aText "Create a new instance with given title and content" ^(self new) title: aTitle; contents: aText; yourself. ! ----- Method: HelpTopic class>>title:icon:contents: (in category 'instance creation') ----- title: aTitle icon: anIcon contents: aText "Create a new instance with given title, icon and content" ^(self new) title: aTitle; icon: anIcon; contents: aText; yourself. ! ----- Method: HelpTopic>>addSubtopic: (in category 'accessing') ----- addSubtopic: aTopic "Adds the given topic to the receivers collection of subtopics" self subtopics add: aTopic. self changed: #subtopicAdded with: aTopic. ^aTopic! ----- Method: HelpTopic>>contents (in category 'accessing') ----- contents "Returns the receivers contents" ^ contents! ----- Method: HelpTopic>>contents: (in category 'accessing') ----- contents: anObject "Sets the receivers contents to the given object" contents := anObject! ----- Method: HelpTopic>>defaultTitle (in category 'defaults') ----- defaultTitle "Returns the receivers default title" ^'Unnamed Topic' ! ----- Method: HelpTopic>>icon (in category 'accessing') ----- icon "Returns the receivers icon" ^icon! ----- Method: HelpTopic>>icon: (in category 'accessing') ----- icon: aSymbol "Sets the receivers icon" icon := aSymbol ! ----- Method: HelpTopic>>initialize (in category 'initialize-release') ----- initialize "Initializes the receiver" super initialize. self title: self defaultTitle. self contents: ''.! ----- Method: HelpTopic>>key (in category 'accessing') ----- key ^ key! ----- Method: HelpTopic>>key: (in category 'accessing') ----- key: aSymbol key := aSymbol.! ----- Method: HelpTopic>>priority (in category 'accessing') ----- priority "A hint for tools to influence sort order." ^ priority! ----- Method: HelpTopic>>priority: (in category 'accessing') ----- priority: anInteger priority := anInteger.! ----- Method: HelpTopic>>sortSubtopicsByTitle (in category 'operating') ----- sortSubtopicsByTitle "Sort the subtopics by title" subtopics := SortedCollection withAll: self subtopics ! ----- Method: HelpTopic>>subtopics (in category 'accessing') ----- subtopics "Returns the receivers list of subtopics" subtopics isNil ifTrue: [subtopics := OrderedCollection new]. ^subtopics! ----- Method: HelpTopic>>subtopics: (in category 'accessing') ----- subtopics: aCollection "Sets the receivers subtopics" subtopics := aCollection ! ----- Method: HelpTopic>>title (in category 'accessing') ----- title "Returns the receivers title" ^ title! ----- Method: HelpTopic>>title: (in category 'accessing') ----- title: anObject "Sets the receivers title" title := anObject! AbstractHelpTopic subclass: #HtmlHelpTopic instanceVariableNames: 'url document selectBlock convertBlock subtopicUrls subtopics level' classVariableNames: '' poolDictionaries: '' category: 'HelpSystem-Core-Model'! ----- Method: HtmlHelpTopic>>contents (in category 'accessing') ----- contents | start end | start := (self document findString: '<body'). start := (self document findString: '>' startingAt: start) + 1. end := self document findString: '</body>' startingAt: start. start > end ifTrue: [^ self document]. ^ ((self document copyFrom: start to: end - 1) copyReplaceAll: String cr with: '<br>') asTextFromHtml! ----- Method: HtmlHelpTopic>>convertBlock (in category 'accessing') ----- convertBlock ^ convertBlock ifNil: [ [:aUrl | aUrl] ]! ----- Method: HtmlHelpTopic>>convertBlock: (in category 'accessing') ----- convertBlock: aBlock convertBlock := aBlock.! ----- Method: HtmlHelpTopic>>document (in category 'accessing') ----- document ^ document ifNil: [document := [ (HTTPSocket httpGet: self url accept: 'text/html') contents ] on: Error do: [:err | err printString]]! ----- Method: HtmlHelpTopic>>fetchSubtopics (in category 'caching') ----- fetchSubtopics "If this method is called from another process than the ui process, there will be no progress shown." | updateBlock | updateBlock := [:topic | topic document; subtopicUrls]. Project current uiProcess == Processor activeProcess ifFalse: [self subtopics do: updateBlock] ifTrue: [self subtopics do: updateBlock displayingProgress: [:topic | 'Fetching documents ... ', topic url]].! ----- Method: HtmlHelpTopic>>hasSubtopics (in category 'testing') ----- hasSubtopics ^ self subtopicUrls notEmpty! ----- Method: HtmlHelpTopic>>isSearchable (in category 'testing') ----- isSearchable ^ self level < 2! ----- Method: HtmlHelpTopic>>level (in category 'accessing') ----- level ^ level ifNil: [level := 1]! ----- Method: HtmlHelpTopic>>level: (in category 'accessing') ----- level: anInteger level := anInteger.! ----- Method: HtmlHelpTopic>>priorityForSearch (in category 'accessing') ----- priorityForSearch ^ 999 "very late"! ----- Method: HtmlHelpTopic>>refresh (in category 'caching') ----- refresh "Re-fetch document and all referenced urls." document := nil. subtopics := nil. self changed: #contents. "See #contents. It is based on document." self changed: #subtopics.! ----- Method: HtmlHelpTopic>>selectBlock (in category 'accessing') ----- selectBlock ^ selectBlock ifNil: [ [:aUrl | true] ]! ----- Method: HtmlHelpTopic>>selectBlock: (in category 'accessing') ----- selectBlock: aBlock "Which urls should be followed?" selectBlock := aBlock.! ----- Method: HtmlHelpTopic>>subtopicUrls (in category 'accessing') ----- subtopicUrls ^ subtopicUrls ifNil: [ | start end | subtopicUrls := OrderedCollection new. start := self document findString: '<a '. start := self document findString: 'href' startingAt: start. [start > 0] whileTrue: [ start := self document findString: '"' startingAt: start. end := self document findString: '"' startingAt: start+1. subtopicUrls addIfNotPresent: (self document copyFrom: start+1 to: end-1). start := self document findString: '<a ' startingAt: end+1. start > 0 ifTrue: [start := self document findString: 'href' startingAt: start]]. subtopicUrls := subtopicUrls select: self selectBlock thenCollect: self convertBlock. subtopicUrls]! ----- Method: HtmlHelpTopic>>subtopics (in category 'accessing') ----- subtopics | start end urls | subtopics ifNotNil: [^ subtopics]. urls := OrderedCollection new. start := self document findString: '<a '. [start > 0] whileTrue: [ start := self document findString: 'href' startingAt: start. start := (self document findString: '"' startingAt: start) + 1. end := self document findString: '"' startingAt: start. urls addIfNotPresent: (self document copyFrom: start to: end - 1). start := self document findString: '<a ' startingAt: start.]. subtopics := (self subtopicUrls collect: [:aUrl | self class new level: self level + 1; url: aUrl; selectBlock: self selectBlock; convertBlock: self convertBlock]). Project current uiProcess == Processor activeProcess ifTrue: [self fetchSubtopics]. ^ subtopics! ----- Method: HtmlHelpTopic>>title (in category 'accessing') ----- title | start end | start := (self document findString: '<title') + 6. start := (self document findString: '>' startingAt: start) + 1. end := self document findString: '</title>' startingAt: start. start > end ifTrue: [^ self url asUrl authority]. ^ self document copyFrom: start to: end - 1! ----- Method: HtmlHelpTopic>>url (in category 'accessing') ----- url ^ url! ----- Method: HtmlHelpTopic>>url: (in category 'accessing') ----- url: aString url := aString.! AbstractHelpTopic subclass: #MethodListHelpTopic instanceVariableNames: 'theClass' classVariableNames: '' poolDictionaries: '' category: 'HelpSystem-Core-Model'! ----- Method: MethodListHelpTopic>>contents (in category 'accessing') ----- contents ^ (String streamContents: [ :stream | self theClass selectors sort do: [ :selector | stream nextPutAll: self theClass name; nextPutAll: '>>'; nextPutAll: selector asString; cr; nextPutAll: ( (self theClass commentsAt: selector) at: 1 ifAbsent: [ '-' ]); cr; cr ] ])! ----- Method: MethodListHelpTopic>>icon (in category 'accessing') ----- icon ^ HelpIcons iconNamed: #pageIcon! ----- Method: MethodListHelpTopic>>priority (in category 'accessing') ----- priority ^ -999! ----- Method: MethodListHelpTopic>>theClass (in category 'accessing') ----- theClass ^ theClass! ----- Method: MethodListHelpTopic>>theClass: (in category 'accessing') ----- theClass: aClassOrMetaClass theClass := aClassOrMetaClass.! ----- Method: MethodListHelpTopic>>title (in category 'accessing') ----- title ^ self theClass isMeta ifTrue: ['Class side'] ifFalse: ['Instance side']! AbstractHelpTopic subclass: #PackageAPIHelpTopic instanceVariableNames: 'packageName' classVariableNames: '' poolDictionaries: '' category: 'HelpSystem-Core-Model'! !PackageAPIHelpTopic commentStamp: 'mt 3/25/2015 15:02' prior: 0! List all classes and their method comments. No subclasses.! ----- Method: PackageAPIHelpTopic>>contents (in category 'accessing') ----- contents ^ ''! ----- Method: PackageAPIHelpTopic>>hasSubtopics (in category 'testing') ----- hasSubtopics ^ SystemOrganization categories anySatisfy: [:cat | (cat beginsWith: self packageName) and: [(SystemOrganization listAtCategoryNamed: cat) notEmpty]]! ----- Method: PackageAPIHelpTopic>>packageName (in category 'accessing') ----- packageName ^ packageName! ----- Method: PackageAPIHelpTopic>>packageName: (in category 'accessing') ----- packageName: aString packageName := aString.! ----- Method: PackageAPIHelpTopic>>subtopics (in category 'accessing') ----- subtopics ^ ((PackageInfo named: self packageName) classes sorted: [:cl1 :cl2 | cl1 name < cl2 name]) collect: [:class | ClassAPIHelpTopic new theClass: class; withSubclasses: false; withMethods: true]! ----- Method: PackageAPIHelpTopic>>title (in category 'accessing') ----- title ^ self packageName! AbstractHelpTopic subclass: #SearchTopic instanceVariableNames: 'term process results resultText topicsToSearch mutex updatePending' classVariableNames: '' poolDictionaries: '' category: 'HelpSystem-Core-Model'! ----- Method: SearchTopic>><= (in category 'comparing') ----- <= anotherHelpTopic ^ true "Keep insertion order in parent topic."! ----- Method: SearchTopic>>contents (in category 'accessing') ----- contents ^ resultText ifNil: [self updateResultText]! ----- Method: SearchTopic>>find:in:results: (in category 'as yet unclassified') ----- find: term in: path results: results | resultTemplate c topic | topic := path last. resultTemplate := Array new: 5. (topic title asString findString: term startingAt: 1 caseSensitive: false) in: [:index | index > 0 ifTrue: [resultTemplate at: 2 put: (index to: index + term size)]]. ((c := topic contents asString withSqueakLineEndings) findString: term startingAt: 1 caseSensitive: false) in: [:index | index > 0 ifTrue: [ | leadingContext trailingContext i | leadingContext := 0. trailingContext := 0. i := index. [i notNil] whileTrue: [ (leadingContext = 2 or: [i = 1]) ifTrue: [ leadingContext := i = 1 ifTrue: [i] ifFalse: [i+1]. i := nil] ifFalse: [ ((c at: i) = Character cr) ifTrue: [ leadingContext := leadingContext + 1]. i := i - 1] ]. i := index + term size. [i notNil] whileTrue: [ (trailingContext = 2 or: [i = c size]) ifTrue: [ trailingContext := i = c size ifTrue: [i] ifFalse: [i-1]. i := nil] ifFalse: [ ((c at: i) = Character cr) ifTrue: [ trailingContext := trailingContext + 1]. i := i + 1] ]. resultTemplate at: 1 put: path; at: 3 put: (index - leadingContext + 1 to: index - leadingContext + term size); at: 4 put: (c copyFrom: leadingContext to: trailingContext); at: 5 put: leadingContext. self mutex critical: [ results add: resultTemplate ]. self triggerUpdateContents. ] ]. topic isSearchable ifTrue: [ topic subtopics do: [:t | self find: term in: path, {t} results: results]].! ----- Method: SearchTopic>>mutex (in category 'as yet unclassified') ----- mutex ^ mutex ifNil: [mutex := Mutex new]! ----- Method: SearchTopic>>printResultEntry: (in category 'as yet unclassified') ----- printResultEntry: entry | resultEntry topic | resultEntry := '' asText. topic := entry first last. entry second notNil ifFalse: [resultEntry append: ( (topic title) asText addAttribute: TextEmphasis bold)] ifTrue: [resultEntry append: ( (topic title) asText addAttribute: TextEmphasis bold; addAttribute: (TextColor color: Color green muchDarker) from: entry second first to: entry second last)]. resultEntry append: (' (open topic)' asText addAttribute: (PluggableTextAttribute evalBlock: [self changed: #searchResultSelected with: entry first])). resultEntry append: String cr. entry fourth in: [:contents | | text | text := contents asText. text addAttribute: (TextColor color: Color green muchDarker) from: entry third first to: entry third last; addAttribute: TextEmphasis bold from: entry third first to: entry third last. resultEntry append: text withBlanksTrimmed; append: '\\' withCRs. ]. ^ resultEntry! ----- Method: SearchTopic>>startSearch (in category 'as yet unclassified') ----- startSearch self stopSearch. results := OrderedCollection new. self topicsToSearch ifEmpty: [ self changed: #contents. ^ self]. process := [ (self topicsToSearch sorted: [:t1 :t2 | t1 priorityForSearch <= t2 priorityForSearch]) do: [:topic | | nestedResults | nestedResults := OrderedCollection new. self mutex critical: [results add: topic -> nestedResults]. self find: self term in: {topic} results: nestedResults]. results add: 'Search finished.'. self triggerUpdateContents. ] forkAt: 35.! ----- Method: SearchTopic>>stopSearch (in category 'as yet unclassified') ----- stopSearch process ifNotNil: #terminate. process := nil.! ----- Method: SearchTopic>>term (in category 'accessing') ----- term ^ term! ----- Method: SearchTopic>>term: (in category 'accessing') ----- term: aString term := aString.! ----- Method: SearchTopic>>title (in category 'accessing') ----- title ^ '''', self term, ''''! ----- Method: SearchTopic>>topicsToSearch (in category 'accessing') ----- topicsToSearch ^ topicsToSearch ifNil: [#()]! ----- Method: SearchTopic>>topicsToSearch: (in category 'accessing') ----- topicsToSearch: someTopics topicsToSearch := someTopics.! ----- Method: SearchTopic>>triggerUpdateContents (in category 'as yet unclassified') ----- triggerUpdateContents self mutex critical: [ updatePending == true ifFalse: [ updatePending := true. Project current addDeferredUIMessage: [ActiveWorld addAlarm: #updateContents withArguments: #() for: self at: Time millisecondClockValue + 250] ] ]. ! ----- Method: SearchTopic>>updateContents (in category 'as yet unclassified') ----- updateContents self mutex critical: [ updatePending := false ]. resultText := nil. self changed: #contents with: self.! ----- Method: SearchTopic>>updateResultText (in category 'as yet unclassified') ----- updateResultText resultText := '' asText. self mutex critical: [ results ifNil: [^ resultText]. results do: [:topicToResult | topicToResult isString ifTrue: [resultText append: ( (topicToResult, String cr) asText addAttribute: (TextColor color: (Color gray: 0.7)); yourself)] ifFalse: [ resultText append: ( ('\----- Matches found in ''', topicToResult key title, ''' -----\\') withCRs asText addAttribute: (TextColor color: (Color gray: 0.7))). topicToResult value do: [:entry | resultText append: (self printResultEntry: entry)] ]]]. ^ resultText! Model subclass: #HelpBrowser instanceVariableNames: 'rootTopic currentTopic currentParentTopic result searchTopic topicPath toplevelTopics oldTopic' classVariableNames: 'DefaultHelpBrowser' poolDictionaries: '' category: 'HelpSystem-Core-UI'! !HelpBrowser commentStamp: 'tbn 3/8/2010 09:33' prior: 0! A HelpBrowser is used to display a hierarchy of help topics and their contents. Instance Variables rootTopic: <HelpTopic> window: <StandardWindow> treeMorph: <PluggableTreeMorph> contentMorph: <Morph> rootTopic - xxxxx window - xxxxx treeMorph - xxxxx contentMorph - xxxxx ! ----- Method: HelpBrowser class>>defaultHelpBrowser (in category 'accessing') ----- defaultHelpBrowser DefaultHelpBrowser isNil ifTrue: [DefaultHelpBrowser := self]. ^DefaultHelpBrowser ! ----- Method: HelpBrowser class>>defaultHelpBrowser: (in category 'accessing') ----- defaultHelpBrowser: aClass "Use a new help browser implementation" DefaultHelpBrowser := aClass ! ----- Method: HelpBrowser class>>initialize (in category 'class initialization') ----- initialize "Initializes the receiver class" TheWorldMenu registerOpenCommand: {'Help Browser'. {self. #open}}. ! ----- Method: HelpBrowser class>>open (in category 'instance creation') ----- open ^self openOn: CustomHelp! ----- Method: HelpBrowser class>>openOn: (in category 'instance creation') ----- openOn: aHelpTopic "Open the receiver on the given help topic or any other object that can be transformed into a help topic by sending #asHelpTopic." ^(self defaultHelpBrowser new) open; rootTopic: aHelpTopic; yourself! ----- Method: HelpBrowser>>accept: (in category 'actions') ----- accept: text "Accept edited text. Compile it into a HelpTopic" | code parent topicClass topicMethod | (self currentParentTopic isNil or: [self currentParentTopic isEditable not]) ifTrue: [^ self inform: 'This help topic cannot be edited.']. parent := self currentParentTopic. topicClass := parent helpClass. topicMethod := self currentTopic key. code := String streamContents:[:s| s nextPutAll: topicMethod. s crtab; nextPutAll: '"This method was automatically generated. Edit it using:"'. s crtab; nextPutAll: '"', self name,' edit: ', topicMethod storeString,'"'. s crtab; nextPutAll: '^HelpTopic'. s crtab: 2; nextPutAll: 'title: ', currentTopic title storeString. s crtab: 2; nextPutAll: 'contents: '. s cr; nextPutAll: (String streamContents:[:c| c nextChunkPutWithStyle: text]) storeString. s nextPutAll:' readStream nextChunkText'. ]. topicClass class compile: code classified: ((topicClass class organization categoryOfElement: topicMethod) ifNil:['pages']). parent refresh. self currentTopic: (parent subtopics detect: [:t | t key = topicMethod]).! ----- Method: HelpBrowser>>buildWith: (in category 'toolbuilder') ----- buildWith: builder | windowSpec treeSpec textSpec searchSpec | windowSpec := builder pluggableWindowSpec new. windowSpec model: self; children: OrderedCollection new; label: #label. searchSpec := builder pluggableInputFieldSpec new. searchSpec model: self; getText: #searchTerm; setText: #searchTerm:; help: 'Search...'; frame: (LayoutFrame fractions: (0@0 corner: 1@0) offsets: (0@0 corner: 0@ (Preferences standardDefaultTextFont height * 2))). windowSpec children add: searchSpec. treeSpec := builder pluggableTreeSpec new. treeSpec model: self; nodeClass: HelpTopicListItemWrapper; roots: #toplevelTopics; getSelected: #currentTopic; setSelected: #currentTopic:; getSelectedPath: #currentTopicPath; setSelectedParent: #currentParentTopic:; autoDeselect: false; frame: (LayoutFrame fractions: (0@0 corner: 0.3@1) offsets: (0@ (Preferences standardDefaultTextFont height * 2) corner: 0@0)). windowSpec children add: treeSpec. textSpec := builder pluggableTextSpec new. textSpec model: self; getText: #topicContents; setText: #accept:; menu: #codePaneMenu:shifted:; frame: (LayoutFrame fractions: (0.3@0.0 corner: 1@1) offsets: (0@ (Preferences standardDefaultTextFont height * 2) corner: 0@0)). windowSpec children add: textSpec. ^ builder build: windowSpec! ----- Method: HelpBrowser>>codePaneMenu:shifted: (in category 'events') ----- codePaneMenu: aMenu shifted: shifted ^ StringHolder basicNew codePaneMenu: aMenu shifted: shifted ! ----- Method: HelpBrowser>>currentParentTopic (in category 'accessing') ----- currentParentTopic ^ currentParentTopic! ----- Method: HelpBrowser>>currentParentTopic: (in category 'accessing') ----- currentParentTopic: aHelpTopic currentParentTopic := aHelpTopic.! ----- Method: HelpBrowser>>currentTopic (in category 'accessing') ----- currentTopic ^ currentTopic! ----- Method: HelpBrowser>>currentTopic: (in category 'accessing') ----- currentTopic: aHelpTopic self currentTopic == aHelpTopic ifTrue: [^ self]. currentTopic := aHelpTopic. topicPath := nil. self changed: #currentTopic. self changed: #topicContents.! ----- Method: HelpBrowser>>currentTopicPath (in category 'accessing') ----- currentTopicPath "Only used for dynamic dispatch. Should be nil or empty on manual evaluation. See #topic:." ^ topicPath ifNil: [#()]! ----- Method: HelpBrowser>>currentTopicPath: (in category 'accessing') ----- currentTopicPath: someTopics "Use the tree structure to select a nested topic." topicPath := someTopics. self changed: #currentTopicPath.! ----- Method: HelpBrowser>>find (in category 'actions') ----- find "Prompt the user for a string to search for, and search the receiver from the current selection onward for it." | reply | reply := UIManager default request: 'Find what? ' initialAnswer: ''. reply size = 0 ifTrue: [ ^ self]. self findStringInHelpTopic: reply ! ----- Method: HelpBrowser>>find: (in category 'actions') ----- find: aString ^SystemNavigation allMethodsSelect: [:method | method hasLiteralSuchThat: [:lit | (lit isString and: [lit isSymbol not]) and: [lit includesSubstring: aString caseSensitive: false]]] localTo: CustomHelp ! ----- Method: HelpBrowser>>findAgain (in category 'actions') ----- findAgain | i | (i := result indexOf: currentTopic) ~= 0 ifTrue: [i = result size ifTrue: [(self confirm: 'Start over?') ifTrue: [i := 1] ifFalse: [^ self]]. self onItemClicked: (result at: i + 1)]! ----- Method: HelpBrowser>>findStringInHelpTopic: (in category 'actions') ----- findStringInHelpTopic: aString result := OrderedCollection new. self inSubtopic: self rootTopic find: aString. result ifNotEmpty: [self topic: result first]. ! ----- Method: HelpBrowser>>inSubtopic:find: (in category 'actions') ----- inSubtopic: aTopic find: aString ((aTopic title asString includesSubstring: aString caseSensitive: false) or: [aTopic contents asString includesSubstring: aString caseSensitive: false]) ifTrue: [result addIfNotPresent: aTopic]. aTopic subtopics do: [:sub | self inSubtopic: sub find: aString]! ----- Method: HelpBrowser>>inTopic:replaceCurrentTopicWith: (in category 'actions') ----- inTopic: parentTopic replaceCurrentTopicWith: aNewTopic parentTopic subtopics do: [ :sub | self inTopic: parentTopic replaceSubtopic: sub with: aNewTopic]! ----- Method: HelpBrowser>>inTopic:replaceSubtopic:with: (in category 'actions') ----- inTopic: parentTopic replaceSubtopic: aTopic with: aNewTopic | i | (aTopic = oldTopic) ifTrue: [ i := parentTopic subtopics indexOf: aTopic. parentTopic subtopics at: i put: aNewTopic. ^self ]. aTopic subtopics do: [ :sub | self inTopic: aTopic replaceSubtopic: sub with: aNewTopic]! ----- Method: HelpBrowser>>label (in category 'accessing - ui') ----- label ^ self rootTopic ifNil: ['Help Browser'] ifNotNil: [:topic | topic title]! ----- Method: HelpBrowser>>menu: (in category 'events') ----- menu: aMenu ^aMenu add: 'find...' translated action: #find. ! ----- Method: HelpBrowser>>open (in category 'ui') ----- open ToolBuilder open: self.! ----- Method: HelpBrowser>>rootTopic (in category 'accessing') ----- rootTopic ^rootTopic! ----- Method: HelpBrowser>>rootTopic: (in category 'accessing') ----- rootTopic: aHelpTopic rootTopic := aHelpTopic asHelpTopic. self toplevelTopics: ((self rootTopic ifNil: [#()] ifNotNil: #subtopics) sorted, {self searchTopic}). self changed: #label.! ----- Method: HelpBrowser>>searchTerm (in category 'searching') ----- searchTerm ^ '' "Reset. Terms are cached in SearchTopic instances."! ----- Method: HelpBrowser>>searchTerm: (in category 'searching') ----- searchTerm: aString "Spawn a new search topic." | topic | topic := self searchTopic subtopics detect: [:t | t term = aString] ifNone: [ | newTopic | newTopic := SearchTopic new term: aString; yourself. self searchTopic addSubtopic: newTopic. newTopic addDependent: self. "Tell me about your updates." newTopic]. "self changed: #searchTerm." "Select results and expand searches node if necessary." self currentTopicPath: {self searchTopic. topic}. self assert: self currentTopic == topic. topic topicsToSearch: self toplevelTopics allButLast; startSearch.! ----- Method: HelpBrowser>>searchTopic (in category 'searching') ----- searchTopic ^ searchTopic ifNil: [searchTopic := HelpTopic new title: 'Search Results'; addDependent: self; yourself]! ----- Method: HelpBrowser>>topicContents (in category 'accessing - ui') ----- topicContents ^ (self currentTopic ifNil: [self rootTopic]) ifNil: '' ifNotNil: #contents! ----- Method: HelpBrowser>>toplevelTopics (in category 'accessing') ----- toplevelTopics ^ toplevelTopics ifNil: [#()]! ----- Method: HelpBrowser>>toplevelTopics: (in category 'accessing') ----- toplevelTopics: someTopics toplevelTopics := someTopics. self changed: #toplevelTopics.! ----- Method: HelpBrowser>>update:with: (in category 'updating') ----- update: aspect with: object aspect == #contents ifTrue: [ object == self currentTopic ifTrue: [self changed: #topicContents]]. aspect == #searchResultSelected ifTrue: [ self currentTopicPath: object].! ----- Method: HelpBrowser>>windowIsClosing (in category 'updating') ----- windowIsClosing super windowIsClosing. self searchTopic subtopics do: [:topic | topic stopSearch].! PluggableListItemWrapper subclass: #HelpTopicListItemWrapper instanceVariableNames: 'parent' classVariableNames: '' poolDictionaries: '' category: 'HelpSystem-Core-UI'! !HelpTopicListItemWrapper commentStamp: 'tbn 3/8/2010 09:30' prior: 0! This class implements a list item wrapper for help topics. Instance Variables ! ----- Method: HelpTopicListItemWrapper class>>with:model:parent: (in category 'as yet unclassified') ----- with: anObject model: aModel parent: aParent ^self new setItem: anObject model: aModel parent: aParent ! ----- Method: HelpTopicListItemWrapper>>asString (in category 'accessing') ----- asString "Returns a string used as a label" ^ self item title! ----- Method: HelpTopicListItemWrapper>>balloonText (in category 'accessing') ----- balloonText "Returns a string used for fly by help" ^self item title! ----- Method: HelpTopicListItemWrapper>>contents (in category 'accessing') ----- contents ^self item subtopics sorted collect: [ :each | HelpTopicListItemWrapper with: each model: self model parent: self] ! ----- Method: HelpTopicListItemWrapper>>hasContents (in category 'accessing') ----- hasContents ^ self item hasSubtopics! ----- Method: HelpTopicListItemWrapper>>icon (in category 'accessing') ----- icon "Either return the icon for the given topic" | symbol | self item icon ifNotNil: [:icon | ^ icon]. symbol := self item hasSubtopics ifTrue: [#bookIcon] ifFalse: [#pageIcon]. ^HelpIcons iconNamed: symbol! ----- Method: HelpTopicListItemWrapper>>item (in category 'accessing') ----- item ^ super item ifNil: [HelpTopic new]! ----- Method: HelpTopicListItemWrapper>>parent (in category 'accessing') ----- parent ^ parent! ----- Method: HelpTopicListItemWrapper>>parent: (in category 'accessing') ----- parent: aWrapper parent := aWrapper.! ----- Method: HelpTopicListItemWrapper>>setItem:model:parent: (in category 'initialization') ----- setItem: anObject model: aModel parent: itemParent self parent: itemParent. self setItem: anObject model: aModel.! ----- Method: HelpTopicListItemWrapper>>update: (in category 'accessing') ----- update: aspect super update: aspect. "Map the domain-specific aspect to a framework-specific one." aspect = #subtopicsUpdated ifTrue: [ self changed: #contents].! ----- Method: HelpTopicListItemWrapper>>update:with: (in category 'accessing') ----- update: aspect with: object super update: aspect with: object. "Map the domain-specific aspect to a framework-specific one." aspect = #subtopicAdded ifTrue: [ self changed: #contents].! Object subclass: #CustomHelp instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'HelpSystem-Core-Utilities'! !CustomHelp commentStamp: 'tbn 3/29/2010 13:23' prior: 0! This is a common superclass for custom help. Subclasses of this class are automatically included into the system help. By default the informations provided on the receiver class are converted into help topics by a specific builder - here the CustomHelpHelpBuilder. Note that you can provide an own custom builder by overriding the #builder method ! ----- Method: CustomHelp class>>accept:title:contents: (in category 'editing') ----- accept: aSelector title: title contents: text "Accept edited text. Compile it into a HelpTopic" | code | code := String streamContents:[:s| s nextPutAll: aSelector. s crtab; nextPutAll: '"This method was automatically generated. Edit it using:"'. s crtab; nextPutAll: '"', self name,' edit: ', aSelector storeString,'"'. s crtab; nextPutAll: '^HelpTopic'. s crtab: 2; nextPutAll: 'title: ', title storeString. s crtab: 2; nextPutAll: 'contents: '. s cr; nextPutAll: (String streamContents:[:c| c nextChunkPutWithStyle: text]) storeString. s nextPutAll:' readStream nextChunkText'. ]. self class compile: code classified: ((self class organization categoryOfElement: aSelector) ifNil:['pages']). ! ----- Method: CustomHelp class>>asHelpTopic (in category 'converting') ----- asHelpTopic "Convert the receiver to a help topic" ^ ClassBasedHelpTopic new helpClass: self! ----- Method: CustomHelp class>>bookName (in category 'accessing') ----- bookName "Returns the name of the custom help book" ^'Help'! ----- Method: CustomHelp class>>edit: (in category 'editing') ----- edit: aSelector "Open a Workspace on the text in the given selector. When accepted, compile the result as a help topic." | topic window | topic := (self respondsTo: aSelector) ifTrue:[self perform: aSelector] ifFalse:[HelpTopic title: 'Untitled' contents: 'Please edit this topic. To change the topic title, edit the window label.']. window := UIManager default edit: topic contents label: topic title accept: [:text| self accept: aSelector title: window label contents: text]. ! ----- Method: CustomHelp class>>icon (in category 'accessing') ----- icon "Returns an icon used for displaying the custom help book" ^HelpIcons iconNamed: #bookIcon! ----- Method: CustomHelp class>>ignore (in category 'accessing') ----- ignore ^ false! ----- Method: CustomHelp class>>pages (in category 'accessing') ----- pages "Returns a collection of method selectors to return the pages of the custom help book" ^#()! ----- Method: CustomHelp class>>priority (in category 'accessing') ----- priority ^ nil! CustomHelp subclass: #HelpOnHelp instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'HelpSystem-Core-Help'! !HelpOnHelp commentStamp: 'dtl 11/13/2014 19:20' prior: 0! HelpOnHelp is documentation for the help system! HelpOnHelp subclass: #HelpAPIDocumentation instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'HelpSystem-Core-Help'! !HelpAPIDocumentation commentStamp: 'tbn 4/30/2010 15:12' prior: 0! This class represents the browsable package API help for the help system. Instance Variables ! ----- Method: HelpAPIDocumentation class>>asHelpTopic (in category 'defaults') ----- asHelpTopic ^ (HelpTopic named: self bookName) subtopics: (self packages collect: [:pkgName | PackageAPIHelpTopic new packageName: pkgName]); yourself! ----- Method: HelpAPIDocumentation class>>bookName (in category 'accessing') ----- bookName ^'API Documentation'! ----- Method: HelpAPIDocumentation class>>packages (in category 'accessing') ----- packages ^#('HelpSystem-Core-Model' 'HelpSystem-Core-Utilities' 'HelpSystem-Core-UI')! HelpOnHelp subclass: #HelpHowToHelpTopics instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'HelpSystem-Core-Help'! ----- Method: HelpHowToHelpTopics class>>bookName (in category 'accessing') ----- bookName ^'Implementation'! ----- Method: HelpHowToHelpTopics class>>overview (in category 'pages') ----- overview ^HelpTopic title: 'Overview' contents: 'THE IMPLEMENTATION The help system typically consists of help books including one or more pages. A book or page is therefore a "topic of interest" providing contents for help to a user. A topic has a title and an icon and is able to have subtopics forming a hierarchy of topics. This simple model is reflected in the class HelpTopic. Since this model forms a hierarchical structure of help topics there is a browser with a tree to display the help contents. This browser is implemented in class HelpBrowser. You can open this browser programmatically using: HelpBrowser open ' ! ----- Method: HelpHowToHelpTopics class>>page1 (in category 'pages') ----- page1 ^HelpTopic title: '1. Simple help topics' contents: 'The help browser usually operates on a hierarchy of help topics with one help topic at the root level. Evaluate the following expression in a workspace to contruct a simple help topic and open it as a root topic in the help browser. |root| root := HelpTopic title: ''My first topic'' contents: ''A simple topic of interest''. HelpBrowser openOn: root Note that the help browser displays the contents of our topic in the right page and uses the topics title as the title for the help browser window. '! ----- Method: HelpHowToHelpTopics class>>page2 (in category 'pages') ----- page2 ^HelpTopic title: '2. Forming a hierarchy' contents: 'To form a hierarchy we just have to add new subtopics on our root topic. |root sub1 sub2| root := HelpTopic title: ''My first topic'' contents: ''A simple topic of interest''. sub1 := HelpTopic title: ''My first subtopic'' contents: ''First subsection''. sub2 := HelpTopic title: ''My second subtopic'' contents: ''Second subsection''. root addSubtopic: sub1; addSubtopic: sub2. HelpBrowser openOn: root '! ----- Method: HelpHowToHelpTopics class>>page3 (in category 'pages') ----- page3 ^HelpTopic title: '3. Adding icons' contents: 'If you dont like the default icon you can add own custom icons to the topics. See the class HelpIcons for more details. |root sub1 sub2| root := HelpTopic title: ''My first topic'' contents: ''A simple topic of interest''. sub1 := HelpTopic title: ''My first subtopic'' contents: ''First subsection''. sub2 := HelpTopic title: ''My second subtopic'' icon: (HelpIcons iconNamed: #packageIcon) contents: ''Second subsection''. root addSubtopic: sub1; addSubtopic: sub2. HelpBrowser openOn: root '! ----- Method: HelpHowToHelpTopics class>>page4 (in category 'pages') ----- page4 ^HelpTopic title: '4. Own help objects' contents: 'You can open this help browser directly on an instance of HelpTopic, but it is more common to open it on any object that understands the message #asHelpTopic. So you can write for instance: HelpBrowser openOn: Integer opening a short API help/system reference on the Integer class. The above expression is the short form for: HelpBrowser openOn: (SystemReference forClass: Integer) If you want you can include the subclasses: HelpBrowser openOn: (SystemReference hierarchyFor: Integer) or even methods HelpBrowser openOn: (SystemReference hierarchyWithMethodsFor: Integer) You can browse the whole system reference documentation using: HelpBrowser openOn: SystemReference But these are only a few examples what we can extract from the system. However - the major goal is NOT an API browser, the idea is to provide a simple architecture to provide browsable help contents depending on the context. For instance it should also be possible to use the help system to provide end user help on any commercial application that is written with the Smalltalk system. ' ! ----- Method: HelpHowToHelpTopics class>>page5 (in category 'pages') ----- page5 ^HelpTopic title: '5. Help sources' contents: 'Since the underlying model is very simple you can easily fill it with nearly any information from different sources. Try this: |topic day url sub| topic := HelpTopic named: ''Last week on Squeak IRC''. 0 to: 7 do: [:index | day := (Date today subtractDays: index) printFormat: #(3 2 1 $. 1 2 2). url := ''http://tunes.org/~nef/logs/squeak/'' , day. sub := HelpTopic title: day contents: (HTTPLoader default retrieveContentsFor: url) contents. topic addSubtopic: sub. ]. HelpBrowser openOn: topic ' ! ----- Method: HelpHowToHelpTopics class>>pages (in category 'accessing') ----- pages ^#(overview page1 page2 page3 page4 page5)! HelpHowToHelpTopics subclass: #HelpHowToHelpTopicsFromCode instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'HelpSystem-Core-Help'! ----- Method: HelpHowToHelpTopicsFromCode class>>bookName (in category 'accessing') ----- bookName ^'Custom help from code'! ----- Method: HelpHowToHelpTopicsFromCode class>>overview (in category 'pages') ----- overview ^HelpTopic title: 'Overview' contents: 'OVERVIEW The help system allows you to provide own books and help texts. You can open the help browser on any object that is able to understand #asHelpTopic. This method returns the root node of the displayed topic hierarchy: HelpBrowser openOn: myObject Typically the object does not convert itself to a help topic structure, usually it dispatches to a builder (see HelpBuilder and subclasses) who does all this. A much more convenient and reproducable way is to implement custom help classes. This allows you to implement and manage your help texts using the standard development and code management tools. These custom help classes are subclasses of "CustomHelp" and are automatically included into the standard help browser. '! ----- Method: HelpHowToHelpTopicsFromCode class>>pages (in category 'accessing') ----- pages ^#(overview step1 step2 step3 step4 step5 step6 step7)! ----- Method: HelpHowToHelpTopicsFromCode class>>step1 (in category 'pages') ----- step1 ^HelpTopic title: 'Step 1 - Create a class for the book' contents: 'STEP 1 - CREATE A CLASS FOR THE BOOK There is a predefined class CustomHelp which you have to subclass for a custom help book to show up as a book in the Help browser: CustomHelp subclass: #MyAppHelp instanceVariableNames: '''' classVariableNames: '''' poolDictionaries: '''' category: ''MyApp-Help'' Class methods on this class can reflect pages and if you want to provide nested help books just subclass your own help class to form a hierarchy. Any new subclass of MyAppHelp will then be a new book in your hierarchy. The class category used should end with "-Help" so it is easy to recognize that it includes the help support of your project.' ! ----- Method: HelpHowToHelpTopicsFromCode class>>step2 (in category 'pages') ----- step2 ^HelpTopic title: 'Step 2 - Provide a book name' contents: 'STEP 2 - PROVIDE A BOOK NAME Now implement the class method #bookName to return the name of your help book. bookName ^''My App help'' By implementing this method the system knows how you would like to name your book and uses the given string as a label in the HelpBrowser later.' ! ----- Method: HelpHowToHelpTopicsFromCode class>>step3 (in category 'pages') ----- step3 ^HelpTopic title: 'Step 3 - Implement pages using methods' contents: 'STEP 3 - IMPLEMENT PAGES USING METHODS Implement a page by defining a method that returns an instance of HelpPage defining a page title and a help text displayed in the help browser. firstPage ^HelpTopic title: ''First Page'' contents: ''Hello world'' Define a new method for each page of your book. Please group the pages in a method category called "pages". You can also define an icon for the specific page: secondPage ^HelpTopic title: ''Second Page'' icon: (HelpIcons iconNamed: #packageIcon) contents: ''More to come'' Note: ===== Later we may add support for better help contents than just plain text (markup descriptions, active morphs, ...) ' ! ----- Method: HelpHowToHelpTopicsFromCode class>>step4 (in category 'pages') ----- step4 ^HelpTopic title: 'Step 4 - Defining the page order' contents: 'STEP 4 - DEFINING THE PAGE ORDER By implementing the class method #pages you return a collection of method selectors to define the order in which the pages appear in your book: pages ^#(firstPage secondPage) ' ! ----- Method: HelpHowToHelpTopicsFromCode class>>step5 (in category 'pages') ----- step5 ^HelpTopic title: 'Step 5 - Test your help' contents: 'STEP 5 - TEST YOUR HELP By using HelpBrowser open ' ! ----- Method: HelpHowToHelpTopicsFromCode class>>step6 (in category 'pages') ----- step6 ^HelpTopic title: 'Step 6 - Add more structure' contents: 'STEP 6 - ADD MORE STRUCTURE If you add a new subclass to your custom help class and repeating step 2 to 4 you can profide new substructures (subbooks) since the help books are mapped to the class hierarchy. Example: MyAppHelp subclass: #MyAppTutorial instanceVariableNames: '''' classVariableNames: '''' poolDictionaries: '''' category: ''MyApp-Help'' then implement a #bookName, the pages and a #pages method as before on this new class and reopen the help browser. ' ! ----- Method: HelpHowToHelpTopicsFromCode class>>step7 (in category 'pages') ----- step7 ^HelpTopic title: 'Step 7 - Tips and Tricks' contents: 'STEP 7 - TIPS AND TRICKS Tip1: If you implement the #pages method you can also use the name of a custom help class that should be integrated between the specific pages: #pages ^(firstPage MyAppTutorial secondPage) Tip2: You can easily edit the help contents of a page by using the #edit: message. For our example just evaluate: MyAppHelp edit: #firstPage This will open a workspace with the help contents and when you accept it it will be saved back to the help method defining the topic. ' ! ----- Method: HelpOnHelp class>>bookName (in category 'accessing') ----- bookName ^'Help on Help'! ----- Method: HelpOnHelp class>>introduction (in category 'pages') ----- introduction "This method was automatically generated. Edit it using:" "HelpOnHelp edit: #introduction" ^HelpTopic title: 'Introduction' contents: 'WELCOME TO THE HELP SYSTEM The help system is a simple user interface to display help contents to the user. It can be accessed from the world menu using "Tools" -> "Help Browser" or by evaluating ''HelpBrowser open'' in a workspace. There is a predefined mechanism allowing you to have help contents stored as source code using methods in specific help provider classes. This allows you to manage the help texts using the standard development tools. But this is only one possible representation. !!' readStream nextChunkText! ----- Method: HelpOnHelp class>>pages (in category 'accessing') ----- pages ^#(introduction HelpHowToHelpTopics HelpAPIDocumentation)! ----- Method: HelpOnHelp class>>priority (in category 'accessing') ----- priority ^ 9999 "at the bottom"! Object subclass: #HelpBuilder instanceVariableNames: 'topicToBuild rootToBuildFrom' classVariableNames: '' poolDictionaries: '' category: 'HelpSystem-Core-Builders'! !HelpBuilder commentStamp: 'tbn 2/12/2010 14:54' prior: 0! This is an utility class that builds the books for a help system. Instance Variables rootTopics: <OrderedCollection> rootTopics - a collection of books ! HelpBuilder subclass: #ClassAPIHelpBuilder instanceVariableNames: 'addSubclasses addMethods subclassesAsSeparateTopic' classVariableNames: '' poolDictionaries: '' category: 'HelpSystem-Core-Builders'! !ClassAPIHelpBuilder commentStamp: 'tbn 4/30/2010 15:37' prior: 0! A builder to build the API Help for a class Instance Variables addMethods: <Boolean> addSubclasses: <Boolean> subclassesAsSeparateTopic: <Boolean> addMethods - When true the builder will include method help addSubclasses - When true the builder will recursively go through and add subclasses subclassesAsSeparateTopic - xxxxx ! ----- Method: ClassAPIHelpBuilder class>>buildHierarchicalHelpTopicFrom:withSubclasses:withMethods: (in category 'building') ----- buildHierarchicalHelpTopicFrom: aClass withSubclasses: aBoolean withMethods: anotherBoolean "Start building from the given class" ^(self new) addSubclasses: aBoolean; addMethods: anotherBoolean; rootToBuildFrom: aClass; build; topicToBuild ! ----- Method: ClassAPIHelpBuilder>>addMethods (in category 'accessing') ----- addMethods ^ addMethods! ----- Method: ClassAPIHelpBuilder>>addMethods: (in category 'accessing') ----- addMethods: anObject addMethods := anObject! ----- Method: ClassAPIHelpBuilder>>addSubclasses (in category 'accessing') ----- addSubclasses ^ addSubclasses! ----- Method: ClassAPIHelpBuilder>>addSubclasses: (in category 'accessing') ----- addSubclasses: anObject addSubclasses := anObject! ----- Method: ClassAPIHelpBuilder>>build (in category 'building') ----- build CurrentReadOnlySourceFiles cacheDuring: [ topicToBuild := (HelpTopic named: rootToBuildFrom name). topicToBuild icon: (HelpIcons iconNamed: #pageIcon). topicToBuild contents: rootToBuildFrom comment. addMethods ifTrue: [ self buildSubnodesForMethods ]. addSubclasses ifTrue: [ self buildSubnodesForSubclasses ] ]. ! ----- Method: ClassAPIHelpBuilder>>buildMethodTopicsOn:for: (in category 'private building') ----- buildMethodTopicsOn: topic for: aClass topic contents: (String streamContents: [ :stream | aClass selectors sort do: [ :selector | stream nextPutAll: aClass name; nextPutAll: '>>'; nextPutAll: selector asString; cr; nextPutAll: ( (aClass commentsAt: selector) at: 1 ifAbsent: [ 'Method has no comment.' ]); cr; cr ] ])! ----- Method: ClassAPIHelpBuilder>>buildSubclassTopicFor: (in category 'private building') ----- buildSubclassTopicFor: aSubclass ^(self class new) rootToBuildFrom: aSubclass; addSubclasses: addSubclasses; addMethods: addMethods; subclassesAsSeparateTopic: subclassesAsSeparateTopic; build; topicToBuild ! ----- Method: ClassAPIHelpBuilder>>buildSubnodesForMethods (in category 'private building') ----- buildSubnodesForMethods | instanceSide classSide | instanceSide := HelpTopic named: 'Instance side'. classSide := HelpTopic named: 'Class side'. topicToBuild icon: (HelpIcons iconNamed: #bookIcon). topicToBuild addSubtopic: instanceSide; addSubtopic: classSide. self buildMethodTopicsOn: instanceSide for: rootToBuildFrom. self buildMethodTopicsOn: classSide for: rootToBuildFrom class. ! ----- Method: ClassAPIHelpBuilder>>buildSubnodesForSubclasses (in category 'private building') ----- buildSubnodesForSubclasses | topic | rootToBuildFrom subclasses isEmpty ifTrue: [^self]. topicToBuild icon: (HelpIcons iconNamed: #bookIcon). topic := subclassesAsSeparateTopic ifTrue: [topicToBuild addSubtopic: (HelpTopic named: 'Subclasses')] ifFalse: [topicToBuild ]. rootToBuildFrom subclasses do: [:subclass | topic addSubtopic: (self buildSubclassTopicFor: subclass)]. topic sortSubtopicsByTitle. ! ----- Method: ClassAPIHelpBuilder>>initialize (in category 'initialize-release') ----- initialize "Initializes the receiver" super initialize. addSubclasses := false. addMethods := true. subclassesAsSeparateTopic := true.! ----- Method: ClassAPIHelpBuilder>>subclassesAsSeparateTopic (in category 'accessing') ----- subclassesAsSeparateTopic ^ subclassesAsSeparateTopic! ----- Method: ClassAPIHelpBuilder>>subclassesAsSeparateTopic: (in category 'accessing') ----- subclassesAsSeparateTopic: anObject subclassesAsSeparateTopic := anObject! HelpBuilder subclass: #CustomHelpHelpBuilder instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'HelpSystem-Core-Builders'! !CustomHelpHelpBuilder commentStamp: 'tbn 3/29/2010 13:30' prior: 0! This builder builds help topics from a help topic description (which is typically stored in a class). The help topic description object has to understand the following messages: #bookName - should return the name of the help book #icon - should return the icon of the help book #key - should return a unique key to identify the book #pages - should return an array of method selectors to call to get the books pages ! ----- Method: CustomHelpHelpBuilder>>build (in category 'building') ----- build "Start building a help topic from a code description" topicToBuild := self createTopicFrom: rootToBuildFrom ! ----- Method: CustomHelpHelpBuilder>>createTopicFrom: (in category 'private') ----- createTopicFrom: aDescription "Create a topic from a description stored on a class. aDescription can specify (via #pages) the name of a class and not only a selector. This allows for hierarchies with 'subtrees in the middle'" |topic page pageClasses | topic := HelpTopic named: aDescription bookName. topic key: aDescription key. topic icon: aDescription icon. pageClasses := Set new. aDescription pages do: [:pageSelectorOrClass| page:= (Smalltalk hasClassNamed: pageSelectorOrClass asString) ifFalse: [aDescription perform: pageSelectorOrClass] ifTrue: [pageClasses add: (Smalltalk classNamed: pageSelectorOrClass asString). (Smalltalk classNamed: pageSelectorOrClass asString) asHelpTopic]. topic addSubtopic: page. ]. ((aDescription subclasses asSet) removeAllFoundIn: pageClasses; yourself) do: [:subclass | topic subtopics add: subclass asHelpTopic ]. ^topic! ----- Method: HelpBuilder class>>buildHelpTopicFrom: (in category 'building') ----- buildHelpTopicFrom: aHelpTopicDescription "Start building from the given help topic description" ^(self new) rootToBuildFrom: aHelpTopicDescription; build; topicToBuild ! ----- Method: HelpBuilder>>build (in category 'building') ----- build self subclassResponsibility ! ----- Method: HelpBuilder>>initialize (in category 'initialize-release') ----- initialize "Initializes the receiver" super initialize. topicToBuild := self topicClass new. ! ----- Method: HelpBuilder>>rootToBuildFrom: (in category 'accessing') ----- rootToBuildFrom: anObject rootToBuildFrom := anObject! ----- Method: HelpBuilder>>topicClass (in category 'private accessing') ----- topicClass ^HelpTopic! ----- Method: HelpBuilder>>topicToBuild (in category 'accessing') ----- topicToBuild ^topicToBuild! HelpBuilder subclass: #PackageAPIHelpBuilder instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'HelpSystem-Core-Builders'! ----- Method: PackageAPIHelpBuilder>>build (in category 'building') ----- build |pTopic| topicToBuild := (HelpTopic named: rootToBuildFrom bookName). rootToBuildFrom packages do: [:package| pTopic := HelpTopic named: package. topicToBuild addSubtopic: pTopic. self buildPackageTopic: pTopic. ] ! ----- Method: PackageAPIHelpBuilder>>buildPackageTopic: (in category 'building') ----- buildPackageTopic: pTopic | classTopic classes | classes := (PackageInfo named: pTopic title) classes asSortedCollection: [:cl1 :cl2 | cl1 name < cl2 name]. classes do: [:aClass| classTopic := ClassAPIHelpBuilder buildHierarchicalHelpTopicFrom: aClass withSubclasses: false withMethods: true. pTopic addSubtopic: classTopic ] ! Object subclass: #HelpIcons instanceVariableNames: '' classVariableNames: 'Icons' poolDictionaries: '' category: 'HelpSystem-Core-UI'! !HelpIcons commentStamp: 'tbn 3/8/2010 09:29' prior: 0! This class is used to store help icons for the help browser. Typically one implements a method returning a 12x12 Form instance which should not be called directly. Since the class provides an internal icon cache (so the icons can be reused without creating too many new instances) the icons should be accessed using the #iconNamed: message with the method selector as argument. To create a form from an icon file stored on disk you can use the following code: | image stream | image := ColorForm fromFileNamed: '/path/to/icon.png'. stream := WriteStream with: String new. image storeOn: stream. stream contents inspect.! ----- Method: HelpIcons class>>blankIcon (in category 'private icons') ----- blankIcon ^Form extent: 12 @ 1 depth:8! ----- Method: HelpIcons class>>bookIcon (in category 'private icons') ----- bookIcon ^(Form extent: 12@12 depth: 32 fromArray: #( 0 0 0 0 0 284817913 552924404 0 0 0 0 0 0 0 0 0 817149108 3747766882 4287730065 2679749049 549766340 0 0 0 0 0 0 1086110908 4016202338 4287137928 4288914339 4288914339 4289111718 3216290996 1086505666 0 0 0 816754350 4014952271 4287137928 4289309097 4289769648 4289111718 4288453788 4288453788 4288453788 2947658161 0 814846353 4283782485 4287072135 4288059030 4288059030 4288387995 4289243304 4289309097 4287927444 4287598479 2411050421 1081900156 4283585106 4286611584 4287532686 4287532686 4287466893 4287466893 4287401100 4287401100 4287401100 4288716960 2946868645 3211290728 4288651167 4287269514 4287006342 4287006342 4287006342 4286940549 4286940549 4287203721 4289177511 3483213213 281725642 2677183122 4293190884 4292861919 4289177511 4286874756 4286611584 4286611584 4287006342 4289638062 4020084125 549042617 0 282054607 2677643673 4289572269 4293256677 4292796126 4288980132 4287137928 4290164406 4020215711 816754350 0 0 0 0 551082200 2677643673 4289572269 4293256677 4292401368 4289177511 1085584564 0 0 0 0 0 0 0 551213786 2677643673 4288651167 1623244992 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0) offset: 0@0)! ----- Method: HelpIcons class>>iconNamed: (in category 'accessing') ----- iconNamed: aSymbol ^self icons at: aSymbol ifAbsentPut: [self perform: aSymbol]! ----- Method: HelpIcons class>>icons (in category 'accessing') ----- icons Icons isNil ifTrue: [Icons := Dictionary new]. ^Icons! ----- Method: HelpIcons class>>packageIcon (in category 'private icons') ----- packageIcon ^(Form extent: 12@12 depth: 32 fromArray: #( 0 0 0 0 1075649821 3744937783 3208395836 807016986 0 0 0 0 0 0 537857807 2939368243 4283256141 4284045657 4284572001 4284111450 2671524924 269488144 0 0 0 2150575919 4014820685 4284111450 4284374622 4284769380 4285098345 4285295724 4286216826 4017057647 1883456323 0 1076505130 4283848278 4284769380 4284966759 4285624689 4285690482 4285887861 4286611584 4287269514 4287861651 4287269514 1074597133 1076965681 4283914071 4283848278 4285953654 4286216826 4286414205 4286940549 4287466893 4287335307 4286808963 4286743170 1074399754 1077163060 4284637794 4284045657 4284835173 4285887861 4287269514 4287335307 4286282619 4286216826 4286874756 4287006342 1074465547 1077294646 4284835173 4284703587 4285361517 4285624689 4286414205 4285624689 4286085240 4286677377 4287269514 4287401100 1074465547 1077426232 4285098345 4285032552 4286019447 4285822068 4286743170 4286348412 4286677377 4287203721 4287730065 4287795858 1074531340 1077492025 4285229931 4285427310 4286808963 4286216826 4287137928 4287072135 4287401100 4287795858 4288256409 4288190616 1074531340 269356558 2672051268 4285493103 4287598479 4286940549 4287532686 4287795858 4287993237 4288387995 4287006342 2404668500 268501249 0 0 1075912993 3479726184 4287598479 4287927444 4288453788 4287993237 2943118444 539371046 0 0 0 0 0 0 1615086660 4017781370 3749148535 1078347334 0 0 0 0) offset: 0@0)! ----- Method: HelpIcons class>>pageIcon (in category 'private icons') ----- pageIcon ^(Form extent: 12@12 depth: 32 fromArray: #( 0 221196079 1366981242 1366915449 1366915449 1366849656 1366783863 1128876361 33554432 0 0 0 0 726552142 4294309365 4294243572 4294111986 4294046193 4293914607 4292861919 2843705215 319885585 0 0 0 726551886 4294177779 4294111986 4293980400 4293914607 4293848814 4293717228 4292138196 3734147730 269619730 0 0 726486349 4294046193 4293980400 4293914607 4293783021 4293717228 4293585642 4293454056 4291085508 639705377 0 0 726420557 4293980400 4293848814 4293783021 4293651435 4293585642 4293519849 4293388263 4292993505 640034342 0 0 726420556 4293848814 4293717228 4293651435 4293585642 4293454056 4293388263 4293256677 4293190884 623322919 0 0 726354764 4293717228 4293651435 4293519849 4293454056 4293322470 4293256677 4293125091 4293059298 623257126 0 0 726354507 4293585642 4293519849 4293388263 4293322470 4293190884 4293125091 4293059298 4292993505 623191333 0 0 726288970 4293454056 4293388263 4293256677 4293190884 4293125091 4292993505 4292993505 4292993505 623191333 0 0 726223178 4293322470 4293256677 4293190884 4293059298 4292993505 4292993505 4292993505 4292993505 623191333 0 0 726223177 4293256677 4293125091 4293059298 4292993505 4292993505 4292993505 4292993505 4292993505 623191333 0 0 490092087 3080033685 3079967892 3079967892 3079967892 3079967892 3079967892 3079967892 3079967892 454629657 0) offset: 0@0)! ----- Method: HelpIcons class>>refreshIcon (in category 'private icons') ----- refreshIcon ^(Form extent: 16@16 depth: 32 fromArray: #( 0 0 0 0 0 0 0 895969127 526080859 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1884706390 4168710521 2288675434 271330348 0 0 0 0 0 0 0 0 0 828465505 2609087363 3615917702 4269439610 4285887861 4285624689 3899156584 1766607948 67569415 0 0 0 0 0 50529027 2306242166 4237069452 4286940549 4286611584 4286282619 4285887861 4285558896 4285229931 4268189543 2235514687 0 0 0 0 0 2590862701 4287598479 4287269514 4270097540 3329652342 3312217196 4285887861 4285558896 3345179491 1011567435 0 0 0 0 0 1263423054 4287532686 4287532686 3867378563 1096835168 0 1885166941 3681579120 1549227863 50923785 0 0 0 0 0 0 3061545851 4287795858 4236937866 811951461 0 0 641547581 137441585 0 0 0 0 0 0 0 34936085 4102720138 4287795858 3011016824 0 0 0 0 0 0 0 0 0 0 0 0 272317243 4287861651 4287795858 2489607268 0 0 0 0 0 0 0 0 68095759 0 0 0 204682035 4287730065 4287795858 2658432116 0 0 0 0 0 0 0 34014983 3965146967 4283979864 3125694030 0 0 3767044232 4287795858 3884287365 137244206 0 0 0 0 0 0 1129863256 4284769380 4284506208 2739423304 0 0 2189459584 4287795858 4287532686 2541123190 16843009 0 0 0 0 305805882 3597166696 4284703587 4250885983 910114623 0 0 273698896 3834218889 4287532686 4287335307 3094442353 1094532413 101255433 286199567 1582124365 3731318631 4284966759 4284703587 2689946965 0 0 0 0 879126118 3985082247 4287269514 4286940549 4286611584 4285624689 4285558896 4285624689 4285229931 4284966759 3227212635 220998700 0 0 0 0 0 509698401 2977659771 4286743170 4286545791 4286282619 4285887861 4285558896 4100417383 2170116441 103295016 0 0 0 0 0 0 0 0 575820370 1734895720 2121298032 2037017194 1448564567 255013683 0 0 0 0 0) offset: 0@0)! Object subclass: #SystemReference instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'HelpSystem-Core-Utilities'! !SystemReference commentStamp: 'tbn 4/30/2010 15:35' prior: 0! This class defines the full reference help for the system. (contents for the full API Help). Just run "HelpBrowser openOn: SystemReference". ! ----- Method: SystemReference class>>all (in category 'help topic creation') ----- all "HelpBrowser openOn: self all " ^(ClassAPIHelpTopic new) theClass: ProtoObject; withSubclasses: true; withMethods: true; subclassesAsSeparateTopic: false! ----- Method: SystemReference class>>asHelpTopic (in category 'help topic creation') ----- asHelpTopic "HelpBrowser openOn: SystemReference" ^self hierarchyFor: ProtoObject ! ----- Method: SystemReference class>>forClass: (in category 'help topic creation') ----- forClass: aClass |root topic | root := HelpTopic named: 'System reference for ', aClass name. topic := ClassAPIHelpTopic new theClass: aClass; withSubclasses: true; withMethods: true; subclassesAsSeparateTopic: false. root addSubtopic: topic. ^root! ----- Method: SystemReference class>>hierarchyFor: (in category 'help topic creation') ----- hierarchyFor: aClass | root topic | root := HelpTopic named: 'System reference for ', aClass name. topic := (ClassAPIHelpTopic new) theClass: aClass; withSubclasses: true; withMethods: false; subclassesAsSeparateTopic: false. root addSubtopic: topic. ^ root! ----- Method: SystemReference class>>hierarchyWithMethodsFor: (in category 'help topic creation') ----- hierarchyWithMethodsFor: aClass | root topic | root := HelpTopic named: 'System reference for ', aClass name. topic := (ClassAPIHelpTopic new) theClass: aClass; withSubclasses: true; withMethods: true; subclassesAsSeparateTopic: true. root addSubtopic: topic. ^ root! ----- Method: Class>>asHelpTopic (in category '*HelpSystem-Core') ----- asHelpTopic ^SystemReference forClass: self! |
Free forum by Nabble | Edit this page |