UIHelpTool.st
author Claus Gittinger <cg@exept.de>
Mon, 11 Sep 2017 09:08:17 +0200
changeset 3489 ed003c06c33b
parent 3402 f5ef58c6082b
child 3499 8808fb1f10b3
permissions -rw-r--r--
#FEATURE by cg class: ImageEditor added: #changePreviewImageMagnification #previewMagnificationHolder changed: #processEvent: #update:with:from: (send #magnification: instead of #magnificationFactor:) class: ImageEditor class changed: #previewMenu

"
 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' }"

"{ NameSpace: Smalltalk }"

ResourceSpecEditor subclass:#UIHelpTool
	instanceVariableNames:'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
! !

!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:'defaults'!

resourceType
    "get the type of the resource of the method generated by the UIHelpTool"

    ^ #help
! !

!UIHelpTool class methodsFor:'help specs'!

flyByHelpSpec
    "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 flyByHelpSpec 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'!

innerSpec
    "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:#innerSpec
     UIHelpTool new openInterface:#innerSpec
    "

    <resource: #canvas>

    ^ 
     #(FullSpec
        name: innerSpec
        window: 
       (WindowSpec
          label: 'UIHelpTool'
          name: 'UIHelpTool'
          min: (Point 10 10)
          bounds: (Rectangle 0 0 461 293)
        )
        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)
            )
           )
         
        )
      )
!

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: 'Help Tool'
         name: 'Help Tool'
         min: (Point 300 300)
         bounds: (Rectangle 0 0 498 429)
         menu: menu
       )
       component: 
      (SpecCollection
         collection: (
          (ViewSpec
             name: 'mainPanel'
             layout: (LayoutFrame 0 0.0 0 0.0 0 1.0 -24 1.0)
             component: 
            (SpecCollection
               collection: (
                (UISubSpecification
                   name: 'innerSpec'
                   layout: (LayoutFrame 0 0.0 0 0.0 0 1.0 -24 1.0)
                   minorKey: innerSpec
                 )
                (UISubSpecification
                   name: 'windowSpecForCommit'
                   layout: (LayoutFrame 0 0.0 -30 1.0 0 1.0 0 1.0)
                   majorKey: ToolApplicationModel
                   minorKey: windowSpecForCommit
                 )
                )
              
             )
           )
          (UISubSpecification
             name: 'infoBarSubSpec'
             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
                  label: 'New'
                  itemValue: doNew
                  translateLabel: true
                )
               (MenuItem
                  label: '-'
                )
               (MenuItem
                  activeHelpKey: fileLoad
                  label: 'Load...'
                  itemValue: doLoad
                  translateLabel: true
                )
               (MenuItem
                  activeHelpKey: fileSave
                  label: 'Save'
                  itemValue: doSave
                  translateLabel: true
                )
               (MenuItem
                  label: '-'
                  isVisible: isStandAlone
                )
               (MenuItem
                  activeHelpKey: fileExit
                  label: 'Exit'
                  itemValue: closeRequest
                  translateLabel: true
                  isVisible: isStandAlone
                )
               )
              nil
              nil
            )
          )
         (MenuItem
            label: 'Edit'
            translateLabel: true
            submenuChannel: keyItemMenu
            keepLinkedMenu: true
          )
         (MenuItem
            label: 'History'
            translateLabel: true
            isVisible: isStandAlone
            submenuChannel: menuHistory
          )
         (MenuItem
            label: 'MENU_Help'
            translateLabel: true
            startGroup: conditionalRight
            submenuChannel: menuHelp
          )
         )
        nil
        nil
      )
! !

!UIHelpTool methodsFor:'accessing'!

alternativeSpecSelectors
    "returns a set of alternative spec method selectors of the resource spec"

    ^ #( helpPairs flyByHelpSpec tooltipSpec )

    "Modified: / 03-02-2017 / 13:05:02 / cg"
!

helpKey
    "returns the helpKey (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 asNilIfEmpty.
        ] 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.
!

enablingCommitButtonsHolder
    "returns the enabling of the commit of this tool as value holder"

    masterApplication notNil ifTrue:[
        ^ masterApplication enablingCommitButtonsHolder
    ].
    ^ contentsModifiedChannel
!

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

infoLabelHolder
    "returns the info label as value holder"

    masterApplication notNil ifTrue:[
        builder aspectAt:#useAlienInfoLabelHolder put:true.
        ^ masterApplication infoLabelHolder
    ].
    ^ super infoLabelHolder
!

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.
! !

!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 loadFromClass: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).
            ]
        ]
    ].
!

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

    (specClass notNil and:[specClass == aClass]) ifTrue:[
        ^ self.
    ].

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

    aClass notNil ifTrue:[
        ((aClass theMetaclass implements:helpSpecSelector)
        and:[(aClass theMetaclass compiledMethodAt:helpSpecSelector) hasResource:#help]) ifFalse:[
            alternativeSelector := (self alternativeSpecSelectors ? #()) 
                                        detect:[:sel | 
                                            (aClass theMetaclass implements:sel)
                                            and:[(aClass theMetaclass compiledMethodAt:sel) hasResource:#help]
                                        ] 
                                        ifNone:nil.
            alternativeSelector notNil ifTrue:[
                helpSpecSelector := specSelector := alternativeSelector.
                specClass := aClass.
            ].
        ].
    ].
    
    list := OrderedCollection new.

    (specClass isClass and:[specClass isLoaded]) ifTrue:[
        lastContents := nil.

        self addHistoryEntryForClass:specClass selector:helpSpecSelector.

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

                (aClass respondsTo: helpSpecSelector) ifTrue:[ 
                    value := aClass perform: helpSpecSelector.
                ].

                value notNil ifTrue:[
                    (value isDictionary) ifTrue:[
                        value keysAndValuesDo:[:k :v| |cval|
                            cval := lastContents at:k ifAbsent:self.
                            cval = v ifFalse:[ root add:(KeyItem helpKey:k helpText:v) ].
                        ].
                        lastContents := value.
                    ] ifFalse:[
                        |dict|
                        dict := Dictionary new.
                        value pairWiseDo:[:k :v| |cval|
                            cval := lastContents at:k ifAbsent:self.
                            cval = v ifFalse:[ root add:(KeyItem helpKey:k helpText:v) ].
                            dict at:k put:v.
                        ].
                        lastContents := dict.
                    ].
                ].
                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)
    ].

    "Modified: / 13-02-2017 / 17:47:02 / cg"
!

loadFromClass:aClass andSelector:aSelector
    "reads the help dictionary from aClass"

    self assert:(aClass isNil or:[aClass isClass]).

    specSelector := aSelector.
    self loadFromClass:aClass
!

loadFromHelpTool: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 enablingCommitButtonsHolder 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|

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

!UIHelpTool methodsFor:'private'!

getHelpSpecClassFromClass:aClass
    |cls|

    aClass isNil ifTrue:[^ nil].

    cls := self resolveName:aClass.
    cls isNil ifTrue:[ ^ nil ].

    cls := cls perform:#helpSpecClass ifNotUnderstood:cls.

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

loadFromMessage:classAndSelector
    "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."

    self askForModification ifFalse:[ ^ false].

    classAndSelector notNil ifTrue:[
        self loadFromClass:(classAndSelector methodClass) andSelector:(classAndSelector methodSelector).
        ^ 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 ifTrue:[
        super closeRequest.
    ].
!

commonPostBuild
    "/ using masters infoHolder ?
    (builder aspectAt:#useAlienInfoLabelHolder) == true ifTrue:[
        (builder componentAt:#mainPanel) layout bottomOffset:0.
        (builder componentAt:#infoBarSubSpec) beInvisible
    ]
!

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 loadFromClass:nil.
!

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

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

    self openInterface:#windowSpec "ForStandAlone".

    builder window label:'Help Tool'.
    self loadFromClass: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.

    "Modified: / 29-08-2006 / 10:20:37 / cg"
!

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 loadFromClass:nil.
!

doSave
    "save the help spec to the spec-class(es)"

    specClass isNil ifTrue:[
        self information:'No class specified !!'.
        ^ nil
    ].
"/ cg: the following test is rubbish !!
"/    (specClass isSubclassOf:ApplicationModel) ifFalse:[
"/        self information:(resources string:'Cannot save help into non-Application class').
"/        ^ nil
"/    ].

    classItemList do:[:aClassItem|
        aClassItem theClass isNil ifTrue:[
            aClassItem onClass:specClass
        ].
        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
    "cg: special case for helpPairs.
     this returns a plain array, not invoking super"
     
    |stream|

    modified ifFalse:[
        ^ self
    ].
    theClass isNil ifTrue:[
        theClass := Dialog requestClass:'Save helpSpec in which class?' initialAnswer:''.
        theClass isNil ifTrue:[
            ^ self
        ].
    ].
    stream := '' writeStream.

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

    <resource: #help>

    ^ #(

'.
    ] ifFalse:[    
        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.

    "Modified: / 13-02-2017 / 17:51:53 / cg"
! !

!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.

    "Modified: / 08-02-2017 / 01:02:14 / cg"
!

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 isEmptyOrNil ifTrue:[ ^ nil ].
    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.

    "Modified: / 08-02-2017 / 01:01:49 / cg"
! !

!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$'
!

version_CVS
    ^ '$Header$'
! !