UIHelpTool.st
author Claus Gittinger <cg@exept.de>
Wed, 19 Mar 2003 09:52:28 +0100
changeset 1690 7428f8744d87
parent 1681 7a8e5df3f07d
child 1723 30d20151ed81
permissions -rw-r--r--
icons

"
 COPYRIGHT (c) 1995 by eXept Software AG
	      All Rights Reserved

 This software is furnished under a license and may be used
 only in accordance with the terms of that license and with the
 inclusion of the above copyright notice.   This software may not
 be provided or otherwise made available to, or used by, any
 other person.  No title to or ownership of the software is
 hereby transferred.
"

"{ Package: 'stx:libtool2' }"

ToolApplicationModel subclass:#UIHelpTool
	instanceVariableNames:'specClass specSelector classItemList classItemModel keyItemModel
		helpTextView modifiedHolder contentsModifiedChannel editModel'
	classVariableNames:''
	poolDictionaries:''
	category:'Interface-UIPainter'
!

HierarchicalItem subclass:#ClassItem
	instanceVariableNames:'theClass list modified'
	classVariableNames:''
	poolDictionaries:''
	privateIn:UIHelpTool
!

HierarchicalItem subclass:#KeyItem
	instanceVariableNames:'helpKey helpText flyByText modified icon'
	classVariableNames:''
	poolDictionaries:''
	privateIn:UIHelpTool
!

!UIHelpTool class methodsFor:'documentation'!

copyright
"
 COPYRIGHT (c) 1995 by eXept Software AG
	      All Rights Reserved

 This software is furnished under a license and may be used
 only in accordance with the terms of that license and with the
 inclusion of the above copyright notice.   This software may not
 be provided or otherwise made available to, or used by, any
 other person.  No title to or ownership of the software is
 hereby transferred.
"
!

documentation
"
    The Help Tool allows you to define help dictionaries for the widgets in
    window applications. The tool are able to run stand alone or in other master 
    applications like the GUI Painter and the Menu Editor.
    If the application responds to the selector #showHelp:aHelpText for:aView,
    this selector is called by the widget's view when the mouse cursor moves over. 
    If the application does not responds to that selector, and the activeHelp mode
    is enabled, an active help bubble is shown at the widget's view.

    [instance variables:]
	specClass       <Symbol>        class implementing the help spec
	specSelector    <Symbol>        selector returning the help spec
	classItemList                   the list of classItems
	classItemModel                  keeps the selected class
	keyItemModel                    keeps the selected helpKey
	modifiedHolder  <ValueHolder>   true if the editField or contents changed
	editModel                       keeps the current helpKey
	helpTextView                    the view which shows the helpText
	contentsModifiedChannel         true if the helpText is modified

    [author:]
	Claus Atzkern, eXept Software AG
	Thomas Zwick,  eXept Software AG
"
! !

!UIHelpTool class methodsFor:'instance creation'!

open
    ^ self openOnClass:nil.
!

openOnClass:aClass
    "opens a Help Tool on aClass
    "
    ^ self openOnClass:aClass andSelector:#helpSpec
!

openOnClass:aClass andSelector: aSelector
    "opens a Help Tool on aClass and aSelector
    "
    ^ self new openOnClass:aClass andSelector:aSelector
! !

!UIHelpTool class methodsFor:'constants'!

label
    "returns the label; used if embedded as sub canvas in the GUI Painter or Menu Editor
    "
    ^'Help'
! !

!UIHelpTool class methodsFor:'help specs'!

helpSpec
    "This resource specification was automatically generated
     by the UIHelpTool of ST/X."

    "Do not manually edit this!! If it is corrupted,
     the UIHelpTool may not be able to read the specification."

    "
     UIHelpTool openOnClass:UIHelpTool    
    "

    <resource: #help>

    ^ super helpSpec addPairsFrom:#(

#addHelpTextKey
'Adds the key to the help spec.'

#currentHelpTexts
'Selected help text key.'

#deleteHelpTextKey
'Deletes the key from the help spec.'

#fileLoad
'Opens a dialog for selecting and loading a help spec from a class.'

#fileSave
'Saves the current help spec.'

#fileUpdate
'Reload the help spec.'

#helpTextView
'Shows the help text. Menu action ''Accept'' commits changes'

#listOfClasses
'Classes where help specs can be/are implemented.'

#listOfHelpTexts
'List of help text keys.'

#removeHelpTextKey
'Removes the help message from the widget.'

#updateHelpTextKey
'Refetch the help spec.'

)
! !

!UIHelpTool class methodsFor:'interface specs'!

windowSpec
    "This resource specification was automatically generated
     by the UIPainter of ST/X."

    "Do not manually edit this!! If it is corrupted,
     the UIPainter may not be able to read the specification."

    "
     UIPainter new openOnClass:UIHelpTool andSelector:#windowSpec
     UIHelpTool new openInterface:#windowSpec
     UIHelpTool open
    "

    <resource: #canvas>

    ^ 
     #(#FullSpec
	#name: #windowSpec
	#window: 
       #(#WindowSpec
	  #label: 'UIHelpTool'
	  #name: 'UIHelpTool'
	  #min: #(#Point 10 10)
	  #max: #(#Point 1024 768)
	  #bounds: #(#Rectangle 30 292 491 585)
	)
	#component: 
       #(#SpecCollection
	  #collection: #(
	   #(#VariableVerticalPanelSpec
	      #name: 'PanelVrt'
	      #layout: #(#LayoutFrame 0 0.0 0 0.0 0 1.0 0 1.0)
	      #component: 
	     #(#SpecCollection
		#collection: #(
		 #(#VariableHorizontalPanelSpec
		    #name: 'PanelHrz'
		    #component: 
		   #(#SpecCollection
		      #collection: #(
		       #(#HierarchicalListViewSpec
			  #name: 'keyItemModel'
			  #model: #keyItemModel
			  #menu: #keyItemMenu
			  #hasHorizontalScrollBar: true
			  #hasVerticalScrollBar: true
			  #miniScrollerHorizontal: true
			  #miniScrollerVertical: false
			  #listModel: #keyItemListHolder
			  #useIndex: false
			  #highlightMode: #label
			  #showLines: false
			  #showIndicators: false
			  #showLeftIndicators: false
			  #useDefaultIcons: false
			  #autoScrollHorizontal: false
			)
		       #(#ViewSpec
			  #name: 'classItemList'
			  #component: 
			 #(#SpecCollection
			    #collection: #(
			     #(#InputFieldSpec
				#name: 'editModel'
				#layout: #(#LayoutFrame 0 0.0 2 0 -1 1.0 25 0)
				#activeHelpKey: #currentHelpTexts
				#model: #editModel
				#immediateAccept: true
				#acceptOnReturn: false
				#acceptOnTab: false
				#acceptOnLostFocus: false
				#acceptOnPointerLeave: false
			      )
			     #(#SelectionInListModelViewSpec
				#name: 'classItemModel'
				#layout: #(#LayoutFrame 0 0.0 27 0.0 0 1.0 0 1.0)
				#model: #classItemModel
				#hasHorizontalScrollBar: true
				#hasVerticalScrollBar: true
				#miniScrollerHorizontal: true
				#miniScrollerVertical: true
				#autoHideScrollBars: false
				#listModel: #classItemListHolder
				#useIndex: false
				#highlightMode: #label
			      )
			     )
                           
			  )
			)
		       )
                     
		    )
		    #handles: #(#Any 0.607375 1.0)
		  )
		 #(#ArbitraryComponentSpec
		    #name: 'helpTextView'
		    #hasHorizontalScrollBar: true
		    #hasVerticalScrollBar: true
		    #miniScrollerHorizontal: true
		    #miniScrollerVertical: true
		    #hasBorder: false
		    #component: #helpTextView
		  )
		 )
               
	      )
	      #handles: #(#Any 0.679181 1.0)
	    )
	   )
         
	)
      )
!

windowSpecForStandAlone
    "This resource specification was automatically generated
     by the UIPainter of ST/X."

    "Do not manually edit this!! If it is corrupted,
     the UIPainter may not be able to read the specification."

    "
     UIPainter new openOnClass:UIHelpTool andSelector:#windowSpecForStandAlone
     UIHelpTool new openInterface:#windowSpecForStandAlone
    "

    <resource: #canvas>

    ^ 
     #(#FullSpec
	#name: #windowSpecForStandAlone
	#window: 
       #(#WindowSpec
	  #label: 'Help Tool'
	  #name: 'Help Tool'
	  #min: #(#Point 300 300)
	  #max: #(#Point 1152 900)
	  #bounds: #(#Rectangle 11 332 509 761)
	  #menu: #menu
	)
	#component: 
       #(#SpecCollection
	  #collection: #(
	   #(#UISubSpecification
	      #name: 'windowSpec'
	      #layout: #(#LayoutFrame 0 0.0 0 0.0 0 1.0 -50 1.0)
	      #minorKey: #windowSpec
	    )
	   #(#UISubSpecification
	      #name: 'windowSpecForCommit'
	      #layout: #(#LayoutFrame 0 0.0 -50 1.0 0 1.0 -24 1.0)
	      #majorKey: #ToolApplicationModel
	      #minorKey: #windowSpecForCommit
	    )
	   #(#UISubSpecification
	      #name: 'windowSpecForInfoBar'
	      #layout: #(#LayoutFrame 0 0 -24 1 0 1 0 1)
	      #majorKey: #ToolApplicationModel
	      #minorKey: #windowSpecForInfoBar
	    )
	   )
         
	)
      )
! !

!UIHelpTool class methodsFor:'menu specs'!

helpTextMenu
    "This resource specification was automatically generated
     by the MenuEditor of ST/X."

    "Do not manually edit this!! If it is corrupted,
     the MenuEditor may not be able to read the specification."

    "
     MenuEditor new openOnClass:UIHelpTool andSelector:#helpTextMenu
     (Menu new fromLiteralArrayEncoding:(UIHelpTool helpTextMenu)) startUp
    "

    <resource: #menu>

    ^ 
     #(#Menu
	#(
	 #(#MenuItem
	    #activeHelpKey: #commitOK
	    #enabled: #contentsModifiedChannel
	    #label: 'Accept'
	    #itemValue: #accept
	    #translateLabel: true
	  )
	 #(#MenuItem
	    #activeHelpKey: #commitCancel
	    #enabled: #contentsModifiedChannel
	    #label: 'Cancel'
	    #itemValue: #cancel
	    #translateLabel: true
	  )
	 )
	nil
	nil
      )
!

keyItemMenu
    "This resource specification was automatically generated
     by the MenuEditor of ST/X."

    "Do not manually edit this!! If it is corrupted,
     the MenuEditor may not be able to read the specification."

    "
     MenuEditor new openOnClass:UIHelpTool andSelector:#keyItemMenu
     (Menu new fromLiteralArrayEncoding:(UIHelpTool keyItemMenu)) startUp
    "

    <resource: #menu>

    ^ 
     #(#Menu
	#(
	 #(#MenuItem
	    #activeHelpKey: #deleteHelpTextKey
	    #label: 'Delete'
	    #itemValue: #doDelete
	    #translateLabel: true
	  )
	 )
	nil
	nil
      )
!

listOfKeysMenu
    "This resource specification was automatically generated
     by the MenuEditor of ST/X."

    "Do not manually edit this!! If it is corrupted,
     the MenuEditor may not be able to read the specification."

    "
     MenuEditor new openOnClass:UIHelpTool andSelector:#listOfKeysMenu
     (Menu new fromLiteralArrayEncoding:(UIHelpTool listOfKeysMenu)) startUp
    "

    <resource: #menu>

    ^ 
     #(#Menu
	#(
	 #(#MenuItem
	    #activeHelpKey: #deleteHelpTextKey
	    #label: 'Delete'
	    #itemValue: #doDelete
	    #translateLabel: true
	  )
	 )
	nil
	nil
      )
!

menu
    "This resource specification was automatically generated
     by the MenuEditor of ST/X."

    "Do not manually edit this!! If it is corrupted,
     the MenuEditor may not be able to read the specification."

    "
     MenuEditor new openOnClass:UIHelpTool andSelector:#menu
     (Menu new fromLiteralArrayEncoding:(UIHelpTool menu)) startUp
    "

    <resource: #menu>

    ^ 
     #(#Menu
	#(
	 #(#MenuItem
	    #label: 'File'
	    #translateLabel: true
	    #submenu: 
	   #(#Menu
	      #(
	       #(#MenuItem
		  #activeHelpKey: #fileLoad
		  #label: 'Load...'
		  #itemValue: #doLoad
		  #translateLabel: true
		)
	       #(#MenuItem
		  #activeHelpKey: #fileSave
		  #label: 'Save'
		  #itemValue: #doSave
		  #translateLabel: true
		)
	       #(#MenuItem
		  #label: 'New'
		  #itemValue: #doNew
		  #translateLabel: true
		)
	       #(#MenuItem
		  #label: '-'
		)
	       #(#MenuItem
		  #activeHelpKey: #fileExit
		  #label: 'Exit'
		  #itemValue: #closeRequest
		  #translateLabel: true
		)
	       )
	      nil
	      nil
	    )
	  )
	 #(#MenuItem
	    #activeHelpKey: #history
	    #label: 'History'
	    #translateLabel: true
	    #submenuChannel: #menuHistory
	  )
	 #(#MenuItem
	    #label: 'Edit'
	    #translateLabel: true
	    #submenuChannel: #keyItemMenu
	    #keepLinkedMenu: true
	  )
	 #(#MenuItem
	    #activeHelpKey: #help
	    #label: 'Help'
	    #translateLabel: true
	    #startGroup: #right
	    #submenuChannel: #menuHelp
	  )
	 )
	nil
	nil
      )
! !

!UIHelpTool methodsFor:'accessing'!

helpKey
    "returns the helpKey as symbol or nil
    "
    |key|

    key := editModel value.

    key size ~~ 0 ifTrue:[
	key := key withoutSeparators.
	key notEmpty ifTrue:[ ^ key asSymbol ]
    ].
    ^ nil
!

helpKey:aKey
    "change the helpKey without any change notification (modifiedHolder).
    "
    |key|

    self withoutModifyDo:[
	aKey size ~~ 0 ifTrue:[
	    key := aKey withoutSeparators.
	    key isEmpty ifTrue:[ key := nil ]
	] ifFalse:[
	    key := nil
	].
	editModel value:key.
    ].
    self cancel.
!

modified
    "true if any items are added, deleted or modified
    "
    classItemList do:[:aClassItem|
	aClassItem modified ifTrue:[^ true].
    ].
    ^ false
!

modified:aBoolean
    "true if any items are added, deleted or modified
    "
    classItemList do:[:aClassItem| aClassItem modified:aBoolean ].
!

modifiedHolder
    "boolean holder which is set to true if the helpKey or contents changed
    "
    ^ modifiedHolder
!

modifiedHolder:aValueHolder
    "boolean holder which is set to true if the helpKey or contents changed
    "
    modifiedHolder notNil ifTrue:[
	modifiedHolder removeDependent:self. 
    ].
    modifiedHolder := aValueHolder.

    modifiedHolder notNil ifTrue:[
	modifiedHolder addDependent:self.
    ].
!

specClass
    "returns the class on which the help tool works on
    "
    ^ specClass
!

specSelector
    "returns the selector of the edited helpSpec method
    "
    ^ specSelector ? #helpSpec
! !

!UIHelpTool methodsFor:'aspects'!

classItemListHolder
    "returns the holder which keeps the class items
    "
    |holder|

    holder := builder bindingAt:#classItemListHolder.

    holder isNil ifTrue:[
	holder := nil asValue.
	holder value:classItemList.
	builder aspectAt:#classItemListHolder put:holder.
    ].
    ^ holder
!

classItemModel
    "returns the holder which keeps the current selected class
    "
    ^ classItemModel
!

contentsModifiedChannel
    "boolean holder, which is set to true if the contents assigned to the
     helpKey changed
    "
    ^ contentsModifiedChannel
!

editModel
    "string holder, which keeps the current editing helpKey as string
    "
    ^ editModel.
!

helpTextView
    "the editView which keeps the current help contents assigned to the key
    "
    ^ helpTextView
!

keyItemListHolder
    "holder, which keeps the current hierarchical list
     assigned to the selected class item
    "
    |holder|

    holder := builder bindingAt:#keyItemListHolder.

    holder isNil ifTrue:[
	holder := nil asValue.
	holder value:(classItemList last list).
	builder aspectAt:#keyItemListHolder put:holder.
    ].
    ^ holder
!

keyItemModel
    "model which keeps the current selected helpKey or nil
    "
    ^ keyItemModel.
!

valueOfEnablingCommitButtons
    "returns the enabling of the commit of this tool as value holder
    "
    masterApplication notNil ifTrue:[
	^ masterApplication valueOfEnablingCommitButtons
    ].
    ^ contentsModifiedChannel
!

valueOfInfoLabel
    "returns the info label as value holder
    "
    masterApplication notNil ifTrue:[
	^ masterApplication valueOfInfoLabel
    ].
    ^ super valueOfInfoLabel
! !

!UIHelpTool methodsFor:'building'!

buildAndMergeFromClass:aClass
     "setup a new specClass, merge the current items into
     "
     |root mergeItems|

     root := classItemList first.

     root isUnspecified ifTrue:[ mergeItems := root children ]
		       ifFalse:[ mergeItems := nil ].

     self buildFromClass:aClass.

     mergeItems size ~~ 0 ifTrue:[
	root := classItemList first.

	mergeItems do:[:anItem| |item hkey|
	    hkey := anItem helpKey.
	    item := root detectItemWithKey:hkey.

	    item isNil ifTrue:[
		item := KeyItem helpKey:hkey helpText:(anItem helpText).
		root add:item sortBlock:[:a :b| a label < b label ].
	    ] ifFalse:[
		item helpText:(anItem helpText).
	    ]
	]
    ].
!

buildFromClass:aClass
    "reads the help dictionary from aClass and find remaining classes 
     'between' aClass and ApplicationModel
    " 
    |lastContents root list resource helpSpecSelector|

    helpSpecSelector := self specSelector.
    specClass := self getHelpSpecClassFromClass:aClass.

    list := OrderedCollection new.

    (specClass isClass and:[specClass isLoaded]) ifTrue:[
        lastContents := nil.
        resource := specClass name, ' ', helpSpecSelector.
        self addToHistory:(Association key:resource value:#'loadFromMessage:').

        specClass withAllSuperclasses reverse do:[:aClass| |value name|
            lastContents isNil ifTrue:[
                aClass == ApplicationModel ifTrue:[ 
                    lastContents := IdentityDictionary new
                ].
            ] ifFalse:[
                root := ClassItem onClass:aClass.

                (aClass class includesSelector:helpSpecSelector) ifTrue:[
                    value := aClass perform:helpSpecSelector.

                    value keysAndValuesDo:[:k :v| |cval|
                        cval := lastContents at:k ifAbsent:self.
                        cval = v ifFalse:[ root add:(KeyItem helpKey:k helpText:v) ].
                    ].
                    lastContents := value.
                ].
                root sort:[:a :b| a label < b label ].
                root modified:false.
                list add:root.
            ]
        ]
    ].
    list isEmpty ifTrue:[
        list add:(ClassItem onClass:nil)
    ].

    self withoutModifyDo:[
        classItemList contents:list.
        self updateIcons.
        classItemModel value:(list last)
    ].
!

buildFromClass: aClass andSelector: aSelector
    "sets aSelector and reads the help dictionary from aClass
    "
    specSelector := aSelector.
    self buildFromClass:aClass
!

buildFromHelpTool:aHelpTool
    "build from another helpTool
    "
    specClass     := aHelpTool specClass.
    specSelector  := aHelpTool specSelector.
    classItemList := aHelpTool classItemListHolder value.

    self classItemListHolder value:classItemList.

    classItemModel triggerValue:(classItemList last).
! !

!UIHelpTool methodsFor:'change & update'!

editModelChanged
    "called if the editModel changed
    "
    |key|

    key := self helpKey.

    modifiedHolder notNil ifTrue:[
	modifiedHolder value:true
    ].

    contentsModifiedChannel value:false.

    key notNil ifTrue:[
	keyItemModel value = key ifTrue:[^ self].

	classItemList reverseDo:[:root| |item|
	    item := root detectItemWithKey:key.

	    item notNil ifTrue:[
		classItemModel value:root.
		keyItemModel   value:item.
		^ self.
	    ].
	].

	masterApplication isNil ifTrue:[
	    "entered a new helpKey
	    "
	    self valueOfEnablingCommitButtons value:true.
	].
    ].
    keyItemModel value:nil.
!

update:something with:aParameter from:changedObject
    "Invoked when an object that I depend upon sends a change notification.
    "
    |root item list|

    changedObject == keyItemModel ifTrue:[
	item := keyItemModel value.

	item notNil ifTrue:[
	    editModel value:(item helpKey).
	].
	self cancel.
	^ self
    ].

    changedObject == classItemModel ifTrue:[
	root := classItemModel value.

	root notNil ifTrue:[
	    item := root detectItemWithKey:(self helpKey).
	    list := root list.
	] ifFalse:[
	    list := item := nil.
	].

	item notNil ifTrue:[
	    keyItemModel value:nil withoutNotifying:self.
	].
	self keyItemListHolder value:list.
	keyItemModel value:item.
	^ self
    ].

    changedObject == editModel ifTrue:[
	self editModelChanged.
	^ self
    ].

    changedObject == contentsModifiedChannel ifTrue:[
	modifiedHolder notNil ifTrue:[
	    modifiedHolder value:true
	].
	^ self
    ].
    super update:something with:aParameter from:changedObject
!

withoutModifyDo:aBlock
    "discard modifications; trigger not the modifiedHolder during
     the action is active.
    "
    |holder|

    holder := modifiedHolder.

    holder isNil ifTrue:[
	^ aBlock value
    ].
    ^ aBlock valueNowOrOnUnwindDo:[ modifiedHolder := holder ]
! !

!UIHelpTool methodsFor:'private'!

askForModification
    "asks for modification; launch a dialog if something is modified;
     returns true if the modifications are accepted by user otherwise
     false.
    "
    |dialog|

    self modified ifTrue:[
	dialog := YesNoBox title:(resources string:'List was modified !!')
			 yesText:(resources string:'Forget it and proceed')
			  noText:(resources string:'Cancel').

	dialog showAtPointer.
	dialog accepted ifFalse:[^ false].
	self modified:false.
    ].
    ^ true
!

extractResourceFrom:aString
    "extracts class and selector from a resource string. On success
     an association with the key a class and the selector as value
     is returned. Otherwise nil is returned
    "
    |words newClass newSel|

    aString size ~~ 0 ifTrue:[
	words := aString asCollectionOfWords.

	words size == 2 ifTrue:[
	    newClass := self resolveName:(words first).

	    (newClass isClass and:[newClass isLoaded]) ifTrue:[
		newSel := words last asSymbol.

		(newClass class includesSelector:newSel) ifTrue:[
		    ^ Association key:newClass value:newSel            
		].
	    ].
	].
    ].
    ^ nil
!

getHelpSpecClassFromClass:aClass
    "oops
    "
    |cls|

    aClass notNil ifTrue:[
	cls := self resolveName:aClass.

	cls notNil ifTrue:[
	    cls := cls perform:#helpSpecClass ifNotUnderstood:cls.

	    (cls isClass and:[cls isLoaded]) ifTrue:[
		^ cls
	    ].
	].
    ].
    ^ nil
!

loadFromMessage:aString
    "Set and rebuild the specClass and specSelector from a resource string.
     On success true is returned otherwise false. If the current spec is
     modified, a dialog is launched.
    "
    |association|

    association := self extractResourceFrom:aString.

    association notNil ifTrue:[
	self askForModification ifTrue:[
	    self buildFromClass:(association key) andSelector:(association value).
	    ^ true
	].
    ].
    ^ false
!

resourceMessage:aString
    "Set the specClass and specSelector from a resource string. On
     success true is returned otherwise false.
    "
    |association|

    association := self extractResourceFrom:aString.

    association notNil ifTrue:[
	specClass    := association key.
	specSelector := association value.
	^ true
    ].
    ^ false
!

updateIcons
    "update all icons (redefinitions below above or both)
    "
    |iconBelow iconAbove iconAboveAndBelow isBehind redefinedAbove redefinedBelow icon|

    "/ if only one class exists, its not possible to have redefinitions
    classItemList size > 1 ifFalse:[ ^ self ].

    iconBelow         := SystemBrowser medium_methodRedefinedBelowIcon.
    iconAbove         := SystemBrowser medium_methodInheritedFromAboveIcon.
    iconAboveAndBelow := SystemBrowser medium_methodInheritedFromAboveAndRedefinedBelowIcon.

    classItemList do:[:runClass|
        runClass do:[:aKeyItem|
            isBehind := redefinedBelow := redefinedAbove := false.

            classItemList do:[:testClass|
                testClass == runClass ifTrue:[
                    isBehind := true
                ] ifFalse:[
                    (testClass detectItemWithKey:(aKeyItem helpKey)) notNil ifTrue:[
                        isBehind ifTrue:[ redefinedBelow := true ]
                                ifFalse:[ redefinedAbove := true ].
                    ]
                ]
            ].

            redefinedBelow ifTrue:[
                redefinedAbove ifTrue:[ icon := iconAboveAndBelow ]
                              ifFalse:[ icon := iconBelow ]
            ] ifFalse:[
                redefinedAbove ifTrue:[ icon := iconAbove ]
                              ifFalse:[ icon := nil ]
            ].
            aKeyItem icon:icon.
        ]
    ].
! !

!UIHelpTool methodsFor:'startup & release'!

closeRequest
    "asks for permission before closing
    "
    (masterApplication isNil and:[self askForModification]) ifTrue:[
	super closeRequest.
    ].
!

initialize
    "setup default attributes
    "
    super initialize.
    self createBuilder.

    specSelector   := #helpSpec.

    classItemList  := List new.

    classItemModel := nil asValue.
    classItemModel addDependent:self.

    keyItemModel := nil asValue.
    keyItemModel addDependent:self.

    contentsModifiedChannel := false asValue.
    contentsModifiedChannel addDependent:self.

    helpTextView := EditTextView new.
    helpTextView acceptAction:[:dummy| self accept ].
    helpTextView modifiedChannel:contentsModifiedChannel.

    editModel := nil asValue.
    editModel addDependent:self.

    self buildFromClass:nil.
!

openInterface:aSymbol
    "do not open as stand alone
    "
!

openOnClass:aClass
    "opens the UIHelpTool on aClass
    "
    self openOnClass:aClass andSelector:nil
!

openOnClass:aClass andSelector: aSelector
    "opens the UIHelpTool on aClass and aSelector"

    super openInterface:#windowSpecForStandAlone.

    builder window label: 'Help Tool'.
    self buildFromClass:aClass andSelector:aSelector
! !

!UIHelpTool methodsFor:'user actions'!

accept
    "accepts the help text
    "
    |helpKey helpItem root|

    helpKey := self helpKey.
    helpKey isNil ifTrue:[^ self].

    root := classItemModel value.
    root isNil ifTrue:[^ self].

    helpItem := root detectItemWithKey:helpKey.

    helpItem isNil ifTrue:[
        helpItem := KeyItem helpKey:helpKey helpText:(helpTextView contents).
        root add:helpItem sortBlock:[:a :b| a label < b label ].
        self updateIcons.
    ] ifFalse:[
        helpItem helpText:(helpTextView contents).
    ].

    contentsModifiedChannel value:false.
    keyItemModel triggerValue:helpItem.
!

cancel
    "cancel modifications, reload helpText
    "
    |item contents modified|

    item := keyItemModel value.
    modified := false.

    item notNil ifTrue:[
	contents := item helpText.
    ] ifFalse:[
	contents := nil.

	modifiedHolder isNil ifTrue:[
	    modified := self helpKey notNil
	]
    ].
    helpTextView contents:contents.
    contentsModifiedChannel value:modified.
!

doDelete
    "deletes the selected help key
    "
    |item|

    item := keyItemModel value.

    item notNil ifTrue:[
        item remove.
        item icon notNil ifTrue:[ self updateIcons ].
    ].    
    editModel value:nil.
!

doLoad
    "opens a Resource Selection Browser in order to get a resource message
    "
    self loadFromMessage: 
        (ResourceSelectionBrowser
            request: 'Load Help Spec From Class'
            onSuperclass: nil
            andClass: specClass
            andSelector: (self specSelector)
            withResourceTypes: (Array with: #help)).

    self updateInfoLabel
!

doNew
    "reset all to empty
    "
    contentsModifiedChannel value:false.
    self helpKey:nil.
    self buildFromClass:nil.
!

doSave
    "save the help spec to the spec-class(es)
    "
    specClass isNil ifTrue:[
        self information:(resources string:'No class specified !!').
        ^ nil
    ].
    (specClass isSubclassOf:ApplicationModel) ifFalse:[
        self information:(resources string:'Cannot save help into none Application class').
        ^ nil
    ].

    classItemList do:[:aClassItem| 
        aClassItem createHelpMethodNamed:(self specSelector) 
    ].
!

openDocumentation
    "opens the documentation file of the Help Tool
    "
    self openHTMLDocument: 'tools/uipainter/HelpTool.html'
! !

!UIHelpTool::ClassItem class methodsFor:'instance creation'!

onClass:aClass
    |root|

    root := self new.
    root onClass:aClass.
    ^ root
! !

!UIHelpTool::ClassItem methodsFor:'accessing'!

list
    "returns the hierarchical list assigned to the classItem; the
     list contains the keyItems
    "
    list isNil ifTrue:[
	list := HierarchicalList new.
	list showRoot:false.
	list root:self.
    ].
    ^ list
!

theClass
    "returns the class or nil if unspecified
    "
    ^ theClass
! !

!UIHelpTool::ClassItem methodsFor:'change & update'!

helpTextChangedFor:anItem
    "called if an helpKey changed its contents
    "
    self model notNil ifTrue:[
	self   modified:true.
	anItem modified:true.
    ].
! !

!UIHelpTool::ClassItem methodsFor:'code generation'!

createHelpMethodNamed:aMethodName
    |stream|

    (modified and:[theClass notNil]) ifFalse:[
        ^ self
    ].
    stream := '' writeStream.

    stream nextPutAll:
        aMethodName, '\' withCRs,
        (ResourceSpecEditor codeGenerationCommentForClass:UIHelpTool) withCRs,
    '\\' withCRs,
    '    "\' withCRs,
    '     UIHelpTool openOnClass:', theClass name asString ,'    
    "

    <resource: #help>

    ^ super ', aMethodName, ' addPairsFrom:#(

'.

    self do:[:aKeyItem| |helpText|
        helpText := aKeyItem helpText.
        helpText isNil ifTrue:[ helpText := '' ].

        stream nextPutLine:(aKeyItem helpKey storeString).
        stream nextPutLine:(helpText storeString); cr.
    ].
    stream nextPutLine:')'.

    Compiler 
        compile:(stream contents)
        forClass:theClass class 
        inCategory:'help specs'.

    self modified:false.
! !

!UIHelpTool::ClassItem methodsFor:'displaying'!

icon
    "returns the display icon (always nil)
    "
    ^ nil
!

label
    "returns the display label
    "
    |label|

    theClass notNil ifTrue:[ label := theClass name ]
		   ifFalse:[ label := '** not yet defined **' ].

    modified ifTrue:[
	label := Text string:label color:(Color red).
    ].
    ^ label
! !

!UIHelpTool::ClassItem methodsFor:'instance creation'!

initialize
    "setup defaults
    "
    super initialize.

    children   := OrderedCollection new.
    isExpanded := true.
    modified   := false.
!

onClass:aClass
    "the class the ys are assigned to; if the class is nil,
     the class is not yet specified.
    "
    theClass := aClass.
! !

!UIHelpTool::ClassItem methodsFor:'private'!

basicAdd:aChild sortBlock:aBlock
    "catch low-level add to update the modification flag
    "
    self   modified:true.
    aChild modified:true.

    ^ super basicAdd:aChild sortBlock:aBlock.
!

basicAddAll:aList beforeIndex:anIndex
    "catch low-level add to update the modification flag
    "
    self modified:true.

    aList do:[:el| el modified:true ].
    ^ super basicAddAll:aList beforeIndex:anIndex.
!

basicRemoveFromIndex:startIndex toIndex:stopIndex
    "catch low-level remove to update the modification flag
    "
    self isUnspecified ifFalse:[
	self modified:true.
    ].
    ^ super basicRemoveFromIndex:startIndex toIndex:stopIndex
! !

!UIHelpTool::ClassItem methodsFor:'queries'!

isUnspecified
    "true if the class is unspecified
    "
    ^ theClass isNil
!

modified
    "true, if any item is modified, created or deleted
    "
    ^ modified
!

modified:aBoolean
    "true, if any item is modified, created or deleted
    "
    modified ~~ aBoolean ifTrue:[
	modified := aBoolean.

	modified ifFalse:[
	    self do:[:el| el modified:false ].
	].
    ].
! !

!UIHelpTool::ClassItem methodsFor:'searching'!

detectItemWithKey:aKey
    "returns the item assigned to a helpKey or nil
    "
    |key|

    aKey size ~~ 0 ifTrue:[
	key := aKey asSymbol.

	self do:[:anItem|
	    anItem helpKey == key ifTrue:[ ^ anItem ].
	]
    ].
    ^ nil
! !

!UIHelpTool::KeyItem class methodsFor:'instance creation'!

helpKey:aKey helpText:aText 
    |key|

    key := self new.
    key helpKey:aKey helpText:aText.
    ^ key
! !

!UIHelpTool::KeyItem methodsFor:'accessing'!

helpKey
    "returns the helpKey, a symbol
    "
    ^ helpKey
!

helpText
    "returns the contents assigned to the helpKey or nil
    "
    ^ helpText
!

helpText:aText
    "set the contents assigned to the helpKey; if the contents changes,
     a notification is raised.
    "
    |text|

    text := self formatText:aText.

    text ~= helpText ifTrue:[
        helpText := text.

        (modified or:[parent isNil]) ifFalse:[
            parent helpTextChangedFor:self.
        ]
    ].
! !

!UIHelpTool::KeyItem methodsFor:'displaying'!

icon
    "returns the display icon (always nil)
    "
    ^ icon
!

icon:anIcon

    icon ~= anIcon ifTrue:[
        icon := anIcon.
        self changed:#icon.
    ].
!

label
    "returns the display label
    "
    modified ifTrue:[
	^ Text string:helpKey color:(Color red)
    ].
    ^ helpKey
! !

!UIHelpTool::KeyItem methodsFor:'instance creation'!

helpKey:aKey helpText:aText
    "set the key and contents without a change notification
    "
    helpKey  := aKey asSymbol.
    helpText := self formatText:aText.
!

initialize
    "setup defaults
    "
    super initialize.
    children := #().
    modified := false.
! !

!UIHelpTool::KeyItem methodsFor:'private'!

formatText:aText
    "format the text, replace carriage return by spaces and compress spaces
    "
    |text result|

    aText size ~~ 0 ifTrue:[
	text := aText asString asCollectionOfWords.

	text notEmpty ifTrue:[
	    result := text first.

	    text from:2 do:[:t| result := result, ' ', t ].
	    ^ result
       ].
    ].
    ^ nil
! !

!UIHelpTool::KeyItem methodsFor:'queries'!

modified
    "returns true if the helpText is modified
    "
    ^ modified
!

modified:aBoolean
    "set the modification flag
    "
    aBoolean == modified ifFalse:[
	modified := aBoolean.
	self changed:#redraw.
    ].
! !

!UIHelpTool class methodsFor:'documentation'!

version
    ^ '$Header$'
! !