ExtendedComboBox.st
author ca
Sun, 15 Oct 2000 16:06:07 +0200
changeset 1868 445f3285bbb2
parent 1857 3c1a9729f46c
child 1890 b08a2a15cea6
permissions -rw-r--r--
checkin from browser

"
 COPYRIGHT (c) 1998 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:libwidg2' }"

View subclass:#ExtendedComboBox
	instanceVariableNames:'menuButton menuField menuWrapper adjust menuWidgetHolder
		menuWidget menuHeight openAction isReadOnly
		usePreferredWidthForMenu hasHorizontalScrollBar
		hasVerticalScrollBar miniScrollerHorizontal miniScrollerVertical
		autoHideScrollBars'
	classVariableNames:''
	poolDictionaries:''
	category:'Views-Interactors'
!

PopUpView subclass:#MenuWrapper
	instanceVariableNames:'comboBox widget lastPointerView implicitGrabView'
	classVariableNames:''
	poolDictionaries:''
	privateIn:ExtendedComboBox
!

!ExtendedComboBox class methodsFor:'documentation'!

copyright
"
 COPYRIGHT (c) 1998 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
"
    An ExtendedComboBox looks much like a ComboBox, but allows for any view
    to be popped up (in contrast to a ComboBox, which has a hardWired selectionInList-
    menu).
    The popped view may optionally be decorated with scrollBars.
    As a side effect, an ExtendedComboBox with a SelectionInListView or Menu
    can now be used as a replacement for ComboBoxes when long-lists are to be shown,
    since those can now be scrolled.

    [author:]
        Claus Atzkern

    [see also:]
        ComboBox ComboView
        PopUpList SelectionInListView
        ComboListView
        PullDownMenu Label EntryField
"

!

examples
"
    example 1: SelectionInListView
                                                                                [exBegin]
    |top menu widget list sidx|

    top  := StandardSystemView extent:200@35.
    menu := ExtendedComboBox origin:5 @ 5 corner:1.0 @ 0.0 in:top.
    list := #('foo' 'bar' 'baz' 'hjh' 'kk' 'claus' 'gjhj').
    sidx := 4.
    menu bottomInset:(menu preferredExtent y negated).
    menu contents:(list at:sidx).

    widget := SelectionInListView new.
    widget list:list.
    widget doubleClickAction:[:i| menu contents:(widget at:i) ].
    widget selection:sidx.
    menu menuWidget:widget.
    top open.
                                                                                [exEnd]



    example 2: FileSelectionTree
                                                                                [exBegin]
    |top menu widget|

    top  := StandardSystemView extent:200@35.
    menu := ExtendedComboBox origin:5 @ 5 corner:1.0 @ 0.0 in:top.
    menu bottomInset:(menu preferredExtent y negated).

    widget := FileSelectionTree new.
    widget directory:(Filename homeDirectory).

    widget doubleClickAction:[:i||n|
        n := widget selectedNode.
        n isDirectory ifFalse:[menu contents:(n pathName)]
    ].
    menu menuHeight:400.
    menu menuWidget:widget.
    top  open.
                                                                                [exEnd]



    example 3: SelectionInTreeView
                                                                                [exBegin]
    |top menu widget|

    top  := StandardSystemView extent:200@35.
    menu := ExtendedComboBox origin:5 @ 5 corner:1.0 @ 0.0 in:top.
    menu bottomInset:(menu preferredExtent y negated).
    menu contents:'foo'.

    widget := SelectionInTreeView new.
    widget root:(TreeItem newAsTreeFromSmalltalkClass:Object).

    widget doubleClickAction:[:i||n|
        n := widget selectedNode.
        n hasChildren ifFalse:[menu contents:(n name)]
    ].
    menu menuHeight:300.
    menu menuWidget:widget.
    top  open.
                                                                                [exEnd]

    example 3: Funny
                                                                                [exBegin]
    |top menu widget|

    top  := StandardSystemView extent:250@35.
    menu := ExtendedComboBox origin:5 @ 5 corner:1.0 @ 0.0 in:top.
    menu bottomInset:(menu preferredExtent y negated).
    menu contents:'lets do the timeWarp again...'.

    widget := ClockView new.
    menu menuHeight:[widget width].
    menu menuWidget:widget.
    top open.
                                                                                [exEnd]


    example 3: Funny - again 
               (use widgets pref-width; even if the combo-box is smaller)
                                                                                [exBegin]
    |top menu widget|

    top  := StandardSystemView extent:80@35.
    menu := ExtendedComboBox origin:5 @ 5 corner:1.0 @ 0.0 in:top.
    menu bottomInset:(menu preferredExtent y negated).
    menu contents:'time...'.

    widget := ClockView new.
    menu menuWidget:widget.
    menu usePreferredWidthForMenu:true.
    top open.
                                                                                [exEnd]


    example 4: Funny - again 
               (use widgets pref-width; even if the combo-box is smaller,
                adjust the height for the width)
                                                                                [exBegin]
    |top menu widget|

    top  := StandardSystemView extent:80@35.
    menu := ExtendedComboBox origin:5 @ 5 corner:1.0 @ 0.0 in:top.
    menu bottomInset:(menu preferredExtent y negated).
    menu contents:'time...'.

    widget := ClockView new.
    menu menuWidget:widget.
    menu usePreferredWidthForMenu:true.
    menu menuHeight:[widget width].
    top open.
                                                                                [exEnd]

    example 5: Subcanvas with a spec
                                                                                [exBegin]
    |top menu widget list sidx spec|

    spec :=  #(#FullSpec
          #window: 
           #(#WindowSpec
              #name: 'Define Color'
              #layout: #(#LayoutFrame 13 0 29 0 352 0 159 0)
              #label: 'Define Color'
              #min: #(#Point 340 110)
              #max: #(#Point 1152 900)
              #bounds: #(#Rectangle 13 29 353 160)
              #usePreferredExtent: false
          )
          #component: 
           #(#SpecCollection
              #collection: 
               #(
                 #(#VerticalPanelViewSpec
                    #name: 'VerticalPanel1'
                    #layout: #(#LayoutFrame 0 0.0 0 0.0 58 0 -40 1.0)
                    #component: 
                     #(#SpecCollection
                        #collection: 
                         #(
                           #(#LabelSpec
                              #name: 'RedLabel'
                              #label: 'Red:'
                              #translateLabel: true
                              #adjust: #right
                              #extent: #(#Point 58 26)
                          )
                           #(#LabelSpec
                              #name: 'GreenLabel'
                              #label: 'Green:'
                              #translateLabel: true
                              #adjust: #right
                              #extent: #(#Point 58 27)
                          )
                           #(#LabelSpec
                              #name: 'BlueLabel'
                              #label: 'Blue:'
                              #translateLabel: true
                              #adjust: #right
                              #extent: #(#Point 58 26)
                          )
                        )
                    )
                    #horizontalLayout: #fit
                    #verticalLayout: #fitSpace
                    #horizontalSpace: 3
                    #verticalSpace: 3
                )
                 #(#VerticalPanelViewSpec
                    #name: 'VerticalPanel2'
                    #layout: #(#LayoutFrame 60 0 0 0.0 -160 1.0 -40 1.0)
                    #component: 
                     #(#SpecCollection
                        #collection: 
                         #(
                           #(#SliderSpec
                              #name: 'RedSlider'
                              #tabable: true
                              #model: #red
                              #orientation: #horizontal
                              #stop: 255
                              #step: 1
                              #backgroundColor: #(#Color 100.0 0.0 0.0)
                              #extent: #(#Point 118 16)
                          )
                           #(#SliderSpec
                              #name: 'GreenSlider'
                              #tabable: true
                              #model: #green
                              #orientation: #horizontal
                              #stop: 255
                              #step: 1
                              #backgroundColor: #(#Color 0.0 100.0 0.0)
                              #extent: #(#Point 118 16)
                          )
                           #(#SliderSpec
                              #name: 'BlueSlider'
                              #tabable: true
                              #model: #blue
                              #orientation: #horizontal
                              #stop: 255
                              #step: 1
                              #backgroundColor: #(#Color 0.0 0.0 100.0)
                              #extent: #(#Point 118 16)
                          )
                        )
                    )
                    #horizontalLayout: #fit
                    #verticalLayout: #spreadSpace
                    #horizontalSpace: 3
                    #verticalSpace: 3
                )
                 #(#VerticalPanelViewSpec
                    #name: 'VerticalPanel3'
                    #layout: #(#LayoutFrame -158 1 0 0.0 -120 1 -40 1.0)
                    #component: 
                     #(#SpecCollection
                        #collection: 
                         #(
                           #(#InputFieldSpec
                              #name: 'RedField'
                              #model: #red
                              #type: #numberInRange
                              #numChars: 3
                              #minValue: 0
                              #maxValue: 255
                              #extent: #(#Point 38 20)
                          )
                           #(#InputFieldSpec
                              #name: 'GreenField'
                              #model: #green
                              #type: #numberInRange
                              #numChars: 3
                              #minValue: 0
                              #maxValue: 255
                              #extent: #(#Point 38 20)
                          )
                           #(#InputFieldSpec
                              #name: 'BlueField'
                              #model: #blue
                              #type: #numberInRange
                              #numChars: 3
                              #minValue: 0
                              #maxValue: 255
                              #extent: #(#Point 38 20)
                          )
                        )
                    )
                    #horizontalLayout: #fit
                    #verticalLayout: #spreadSpace
                    #horizontalSpace: 3
                    #verticalSpace: 3
                )
                 #(#LabelSpec
                    #name: 'PreviewBox'
                    #layout: #(#LayoutFrame -116 1 0 0.0 -2 1.0 -40 1.0)
                    #label: 'Preview'
                    #translateLabel: true
                    #level: -1
                )
                 #(#HorizontalPanelViewSpec
                    #name: 'HorizontalPanel1'
                    #layout: #(#LayoutFrame 0 0.0 -32 1 0 1.0 0 1.0)
                    #component: 
                     #(#SpecCollection
                        #collection: 
                         #(
                           #(#ActionButtonSpec
                              #name: 'CancelButton'
                              #label: 'Cancel'
                              #translateLabel: true
                              #model: #cancel
                              #extent: #(#Point 165 26)
                          )
                           #(#ActionButtonSpec
                              #name: 'OKButton'
                              #label: 'OK'
                              #translateLabel: true
                              #model: #accept
                              #extent: #(#Point 166 26)
                          )
                        )
                    )
                    #horizontalLayout: #fitSpace
                    #verticalLayout: #centerMax
                    #horizontalSpace: 3
                    #verticalSpace: 3
                )
              )
          )
      ).

    top  := StandardSystemView extent:200@35.
    menu := ExtendedComboBox origin:5 @ 5 corner:1.0 @ 0.0 in:top.
    list := #('foo' 'bar' 'baz' 'hjh' 'kk' 'claus' 'gjhj').
    sidx := 4.
    menu bottomInset:(menu preferredExtent y negated).
    menu contents:(list at:sidx).

    widget := SubCanvas new.
    widget spec:spec.
    menu menuWidget:widget.
    top open.
                                                                                [exEnd]




"
! !

!ExtendedComboBox class methodsFor:'testing'!

tests
        |top menu|

        top  := StandardSystemView extent:200@35.
        menu := ExtendedComboBox origin:5 @ 5 corner:0.8 @ 0.0 in:top.
        menu bottomInset:(menu preferredExtent y negated).
        menu list:#('foo' 'bar' 'baz' 'hjh' 'kk' 'claus' 'gjhj').
        top  open.

"
    test1
        |top menu|

        top  := StandardSystemView extent:200@35.
        menu := ExtendedComboBox origin:5 @ 5 corner:0.8 @ 0.0 in:top.
        menu bottomInset:(menu preferredExtent y negated).
        menu list:#('foo' 'bar' 'baz' 'hjh' 'kk' 'claus' 'gjhj').
        top  open.


    test2
        |top menu view edit|

        top  := StandardSystemView extent:200@35.
        menu := ExtendedComboBox origin:5 @ 5 corner:195 @ 25 in:top.
        view := View extent:180 @ 100.
        edit := EditField origin:5@5 in:view.
        edit width:175.

        edit := EditField origin:5@30 in:view.
        edit width:175.

        menu menuWidget:view.
        menu menuHeight:100.
        top  open.

    test3
        |top menu view edit b|

        top  := StandardSystemView extent:200@35.
        menu := ExtendedComboBox origin:5 @ 5 corner:195 @ 25 in:top.
        view := View extent:180 @ 100.
        edit := EditField origin:5@5 in:view.
        edit width:175.

        edit := EditField origin:5@30 in:view.
        edit width:175.

        b := Button label:'button' in:view.
        b origin:5@50. 
        b action:[ Transcript showCR:'.. things to do, when pressed ...' ].

        menu menuWidget:view.
        menu menuHeight:100.
        top  open.

    test4
        |top menu view edit b|

        top  := StandardSystemView extent:200@35.
        menu := ExtendedComboBox origin:5 @ 5 corner:195 @ 25 in:top.
        view := View extent:180 @ 100.
        edit := HVScrollableView for:EditTextView origin:0@0 corner:1.0@1.0 in:view.
        edit contents:'Makefile' asFilename contents.
        menu menuWidget:view.
        menu menuHeight:100.
        top  open.

    test5
        |top menu view|

        top  := StandardSystemView extent:300@35.
        menu := ExtendedComboBox origin:0.0@0.0 corner:1.0@1.0 in:top.

        view := View new.
        'view client:(CodingExamples_GUI::GUIDemoDataSetView new)'.
        view client:(CodingExamples_GUI::GUIDemoDragAndDrop new).

        menu menuWidget:view.
        menu menuHeight:300.
        top  open.

    test6
        |top menu view|

        top  := StandardSystemView extent:300@35.
        menu := ExtendedComboBox origin:0.0@0.0 corner:1.0@1.0 in:top.

        view := View new.
        view client:(CodingExamples_GUI::GUIDemoDataSetView new).

        menu menuWidget:view.
        menu menuHeight:300.
        top  open.

    test7
        |top menu view|

        top  := StandardSystemView extent:300@35.
        menu := ExtendedComboBox origin:0.0@0.0 corner:1.0@1.0 in:top.

        view := View new.
        view client:(CodingExamples_GUI::GUIDemoTabs new).

        menu menuWidget:view.
        menu menuHeight:300.
        top  open.

    test8
        |top menu view|

        top  := StandardSystemView extent:300@35.
        menu := ExtendedComboBox origin:0.0@0.0 corner:1.0@1.0 in:top.

        view := View new.
        view client:(CodingExamples_GUI::GUIDemoSliders new).

        menu menuWidget:view.
        menu menuHeight:300.
        top  open.

    test9
        |top menu view|

        top  := StandardSystemView extent:400@35.
        menu := ExtendedComboBox origin:0.0@0.0 corner:1.0@1.0 in:top.

        view := View new.
        view client:(CodingExamples_GUI::GUIDemo new).

        menu menuWidget:view.
        menu menuHeight:500.
        top  open.

    test10
        |top menu view o|

        top  := StandardSystemView extent:300@35.
        menu := ExtendedComboBox origin:0.0@0.0 corner:1.0@1.0 in:top.

        view := DrawView new.
        o := DrawRectangle new.
        o origin:10@10 corner:100@100.
        view add:o.

        o := DrawText new.
        o text:'hello there'; origin:50@50; foreground:Color red.
        view add:o.


        menu menuWidget:view.
        menu menuHeight:300.
        top  open.

"
! !

!ExtendedComboBox methodsFor:'accessing'!

contents
    "return the value of the field
    "
    ^ model value
!

contents:aValue
    "set the value of the field
    "
    model value:aValue.
!

menuWidget
    "get the menu widget or nil
    "
    ^ menuWidget


!

menuWidget:aWidgetOrNil
    "set the menu widget or nil
    "
    menuWidget ~~ aWidgetOrNil ifTrue:[
        menuWrapper notNil ifTrue:[
            self closeMenu.
            menuWrapper destroy.
            menuWrapper := nil.
        ].
        aWidgetOrNil isView ifTrue:[
            menuWidget := aWidgetOrNil.
          ^ self
        ].

        aWidgetOrNil isFilename ifTrue:[
            ^ self directory:aWidgetOrNil
        ].

        aWidgetOrNil isSequenceable ifTrue:[
            ^ self list:aWidgetOrNil
        ]
    ].
! !

!ExtendedComboBox methodsFor:'accessing-actions'!

openAction
    "returns the action, called before opening the pulldown menu
    "
    ^ openAction
!

openAction:aOneArgBlock
    "set the action, called before opening the pulldown menu; the
     argument to the action is the menu widget
    "
    openAction := aOneArgBlock
! !

!ExtendedComboBox methodsFor:'accessing-behavior'!

disable
    "disable components
    "
    self closeMenu.

    (menuField respondsTo:#disable) ifTrue:[
        menuField disable
    ].
    menuButton disable

!

enable
    "enable components
    "
    (menuField respondsTo:#enable) ifTrue:[
        menuField enable
    ].
    menuButton enable


!

enabled
    "returns true, if is enabled
    "
    ^ menuButton enabled


! !

!ExtendedComboBox methodsFor:'accessing-channels'!

enableChannel 
    "return a valueHolder for enable/disable
    "
    ^ menuButton enableChannel


!

enableChannel:aValueHolder 
    "set a valueHolder for enable/disable for the components
    "
    (menuField respondsTo:#enableChannel:) ifTrue:[
        menuField enableChannel:aValueHolder
    ].
    menuButton enableChannel:aValueHolder.


!

menuWidgetHolder
    "returns the menu widget or nil
    "
    ^ menuWidgetHolder
!

menuWidgetHolder:aHolder
    "returns the menu widget or nil
    "
    menuWidgetHolder notNil ifTrue:[
        menuWidgetHolder removeDependent:self
    ].

    (menuWidgetHolder := aHolder) notNil ifTrue:[
        menuWidgetHolder addDependent:self
    ].
    self menuWidget:(menuWidgetHolder value).
!

model:aModel
    "set the model
    "
    super model:(aModel ? ValueHolder new).
    menuField notNil ifTrue:[ menuField model:model ].
! !

!ExtendedComboBox methodsFor:'accessing-components'!

menuField
    ^ menuField
!

menuHolder:anObject
    "change the one that provides the menu (via menuMsg)."

    super menuHolder:anObject.
    menuField notNil ifTrue:[
        menuField menuHolder:anObject
    ]
! !

!ExtendedComboBox methodsFor:'accessing-default menus'!

directory:aDirectory
    "creates a FileSelectionTree - menu,
     setting the root to the directory
    "
    menuWidget isNil ifTrue:[
        self autoHideScrollBars:false.
        menuWidget := FileSelectionTree new.

        menuWidget doubleClickAction:[:aDummy||node|
            node := menuWidget selectedNode.

            node isDirectory ifFalse:[
                self contents:(node pathName)
            ]
        ]
    ].
    self menuHeight:400.
    menuWidget directory:aDirectory.
!

list:aList
    "creates a SelectionInListView - menu
     setting the list
    "
    menuWidget isNil ifTrue:[
        menuWidget := SelectionInListView onDevice:(device ? Screen current).

        menuWidget doubleClickAction:[:anIndex| 
            self contents:(menuWidget at:anIndex)
        ]
    ].
    menuWidget list:aList
! !

!ExtendedComboBox methodsFor:'accessing-dimension'!

menuHeight
    "get the maximum height of the menu widget or nil
    "
    ^ menuHeight
!

menuHeight:aHeight
    "set the maximum height of the menu widget or nil
    "
    menuHeight := aHeight.
!

preferredExtent
    "compute & return the preferredExtent from the components' preferrences
    "
    |fieldPref buttonPref m w h f|

    "/ If I have an explicit preferredExtent ..

    preferredExtent notNil ifTrue:[
        ^ preferredExtent
    ].
    fieldPref  := menuField preferredExtent.
    buttonPref := menuButton preferredExtent.
    f := 2 + font maxHeight + (font maxDescent * 2) + (menuField margin * 2).
    h := (fieldPref y max:f) max:buttonPref y.
    m := margin max:1.
    h := h + m + m.

    w := (fieldPref x max:100) + buttonPref x.

    menuWidget notNil ifTrue:[
        f := menuWidget preferredExtent x "widthOfContents" + buttonPref x + 20.
        w := f max:w
    ].
    w := w + margin + margin.

    ^ w @ h

!

usePreferredWidthForMenu
    "returns true if the menu is to be opened with its menu preferred width
    "
    ^ usePreferredWidthForMenu

!

usePreferredWidthForMenu:aBoolean
    "open the menu with its preferred width
    "
    usePreferredWidthForMenu := aBoolean
! !

!ExtendedComboBox methodsFor:'accessing-look'!

adjust
    "returns the button adjust symbol, which is one of
         #left  -> left adjust  button
         #right -> right adjust button
    "
    ^ adjust ? #right

!

adjust:how
    "set the button adjust, which must be one of
         #left  -> left adjust  button
         #right -> right adjust button
    "
    |rightInset|

    (adjust ~~ how and:[(how == #left or:[how == #right])]) ifTrue:[
        (adjust := how) == #left ifTrue:[
            menuButton origin:0.0@0.0 corner:0.0@1.0
        ] ifFalse:[
            menuButton origin:1.0@0.0 corner:1.0@1.0
        ].

        rightInset := menuButton leftInset.
        menuButton leftInset:(menuButton rightInset).
        menuButton rightInset:rightInset.

        rightInset := menuField leftInset.
        menuField  leftInset:(menuField rightInset).
        menuField rightInset:rightInset.
    ].
!

backgroundColor
    "get the background color of the menu field
    "
    ^ menuField backgroundColor


!

backgroundColor:aColor
    "set the background color of the menu field
    "
    menuField backgroundColor:aColor

!

font:aFont
    "set the font of the menu field
    "
    super font:aFont.
    menuField font:aFont.



!

foregroundColor
    "set the foreground color of the menu field
    "
    ^ menuField foregroundColor

!

foregroundColor:aColor
    "set the foreground color of the menu field
    "
    menuField foregroundColor:aColor

!

readOnly
    "returns true if the menuField is readonly, a label
    "
    ^ isReadOnly
!

readOnly:aState
    "set the menuField to readonly or writable
    "
    |newField nm|

    isReadOnly == aState ifTrue:[ ^ self ].
    isReadOnly := aState.

    aState ifTrue:[
        newField := Label in:self.
        newField level:-1.
        newField adjust:#left.
        newField labelMessage:#value.
    ] ifFalse:[
        newField := EditField in:self
    ].

    newField origin:0.0 @ 0.0 corner:1.0 @ 1.0.

    menuField notNil ifTrue:[
        newField      level:(menuField level).
        newField  leftInset:(menuField leftInset).
        newField rightInset:(menuField rightInset).

        menuField destroy.
    ] ifFalse:[
        styleSheet is3D ifTrue:[
            (styleSheet at:'comboViewLevel' default:nil) notNil ifTrue:[
                newField level:0.
            ] ifFalse:[
                newField leftInset:(ViewSpacing // 2).
            ]
        ].
        nm := styleSheet name.
        (nm = #win95 or:[nm = #win98 or:[nm = #st80]]) ifTrue:[
            newField level:0.
            newField leftInset:0.
        ].
        newField rightInset:(menuButton leftInset negated).
    ].

    menuField := newField.
    menuField model:model.
    menuField  font:font.
    self shown ifTrue:[ menuField realize ].


! !

!ExtendedComboBox methodsFor:'accessing-scroller'!

autoHideScrollBars
    "set/clear the flag which controls if scrollBars should
     be made invisible dynamically, if there is nothing to scroll
     (and shown if there is)
    "
    ^ autoHideScrollBars


!

autoHideScrollBars:aBoolean
    "set/clear the flag which controls if scrollBars should
     be made invisible dynamically, if there is nothing to scroll
     (and shown if there is)
    "
    autoHideScrollBars := aBoolean

!

hasHorizontalScrollBar
    "enable/disable horizontal scrollability.
     If disabled, the horizontal scrollBar is made invisible.
    "
    ^ hasHorizontalScrollBar


!

hasHorizontalScrollBar:aBool
    "enable/disable horizontal scrollability.
     If disabled, the horizontal scrollBar is made invisible.
    "
    hasHorizontalScrollBar := aBool

!

hasVerticalScrollBar
    "enable/disable vertical scrollability.
     If disabled, the vertical scrollBar is made invisible.
    "
    ^ hasVerticalScrollBar
!

hasVerticalScrollBar:aBool
    "enable/disable vertical scrollability.
     If disabled, the vertical scrollBar is made invisible.
    "
    hasVerticalScrollBar := aBool

!

miniScrollerHorizontal
    "control the horizontal scrollBar to be either a miniScroller,
     or a full scrollBar.
    "
    ^ miniScrollerHorizontal
!

miniScrollerHorizontal:aBool
    "control the horizontal scrollBar to be either a miniScroller,
     or a full scrollBar.
    "
    miniScrollerHorizontal := aBool

!

miniScrollerVertical
    "control the vertical scrollBar to be either a miniScroller,
     or a full scrollBar.
    "
    ^ miniScrollerVertical


!

miniScrollerVertical:aBool
    "control the vertical scrollBar to be either a miniScroller,
     or a full scrollBar.
    "
    miniScrollerVertical := aBool

! !

!ExtendedComboBox methodsFor:'event handling'!

doesNotUnderstand:aMessage
    "does not understand message; delegate to widget
    "
    menuWidget notNil ifTrue:[
        ^ aMessage sendTo:menuWidget
    ].
    ^ super doesNotUnderstand:aMessage

!

keyPress:key x:x y:y
    "handle a key press event
    "
    (key == Character space or:[key == #Return]) ifTrue:[
        self enabled ifTrue:[
            self openMenu
        ]
    ] ifFalse:[
        super keyPress:key x:x y:y
    ]

!

update:what with:aPara from:aModel
    "one of my models changed
    "
    aModel == self model ifTrue:[
        ^ self closeMenu
    ].
    aModel == self menuWidgetHolder ifTrue:[
        ^ self menuWidget:(menuWidgetHolder value)
    ].
    super update:what with:aPara from:aModel


! !

!ExtendedComboBox methodsFor:'initialization'!

destroy
    "destroy the menuWrapper and release dependencies
    "
    menuWidgetHolder notNil ifTrue:[
        menuWidgetHolder removeDependent:self
    ].
    self  menuWidget:nil.
    super destroy.
!

initialize
    "setup defaults
    "
    |prefExt leftInset halfSpacing l nm|

    super initialize.

    menuButton := ComboBoxButton origin:1.0@0.0 corner:1.0@1.0 in:self.
    menuButton controller beTriggerOnDown.
    menuButton label:(ComboView buttonForm).
    menuButton showLamp:false.

    menuButton activeLevel == menuButton passiveLevel ifTrue:[
        menuButton activeLevel:0.
    ].
    menuButton pressAction:[self openMenu].

    prefExt := menuButton preferredExtent.

    styleSheet is3D ifTrue:[
        halfSpacing := ViewSpacing // 2.
        leftInset   := prefExt x + halfSpacing.

        (l := styleSheet at:'comboViewLevel' default:nil) notNil ifTrue:[
            self level:l.
        ] ifFalse:[
            menuButton rightInset:halfSpacing.
        ]
    ] ifFalse:[
        leftInset := prefExt x + menuButton borderWidth.
    ].

    nm := styleSheet name.
    (nm = #win95 
    or:[nm = #win98 
    or:[nm = #st80]]) ifTrue:[
        self level:-1.
        menuButton rightInset:0.

        nm ~= #st80 ifTrue:[
            leftInset := (ArrowButton new preferredExtent x).
        ].
    ].
    menuButton leftInset:leftInset negated.
    self model:nil.

    adjust                   := #right.
    usePreferredWidthForMenu := false.
    isReadOnly               := false.

    autoHideScrollBars       := true.
    hasHorizontalScrollBar   := true.
    hasVerticalScrollBar     := true.
    miniScrollerHorizontal   := true.
    miniScrollerVertical     := true.

    self readOnly:true.

! !

!ExtendedComboBox methodsFor:'queries'!

menuIsScrollable
    "returns true if the menu is scrollable
    "
    ^ (self hasVerticalScrollBar or:[self hasHorizontalScrollBar])
! !

!ExtendedComboBox methodsFor:'user interactions'!

closeMenu
    "close the menu
    "
    |id|

    menuWrapper notNil ifTrue:[
        menuWrapper realized ifFalse:[
            (id := menuWrapper id) notNil ifTrue:[
                device unmapWindow:id
            ]
        ] ifTrue:[
           menuWrapper unmap
        ].
"/        menuWrapper windowGroup:nil.
"/        self windowGroup removeView:menuWrapper.
    ].
    menuButton turnOff.

!

openMenu
    "pull the menu - triggered from the button
    "
    |h w menuOrigin widgetPrfExt useableExt|

    openAction notNil ifTrue:[
        openAction numArgs == 0 ifTrue:[
            openAction value
        ] ifFalse:[
            openAction value:menuWidget
        ]
    ].
    menuWrapper isNil ifTrue:[
        menuWidget isNil ifTrue:[^ self].
        menuWrapper := MenuWrapper onDevice:(device ? Screen current).
        menuWrapper for:menuWidget in:self.
    ].
    menuButton turnOn.

    menuOrigin   := device translatePoint:(0@height) from:(self id) to:(device rootWindowId).
    useableExt   := device usableExtent.
    widgetPrfExt := menuWrapper preferredExtent.

    menuHeight isNil ifTrue:[
        menuHeight := (5 + widgetPrfExt y) min:(useableExt y // 2).
    ].

    usePreferredWidthForMenu ifFalse:[
        w := width.
    ] ifTrue:[
        (w := widgetPrfExt x+(menuWrapper borderWidth*2)) <= width ifTrue:[
            w := width
        ] ifFalse:[
            (w + menuOrigin x) > useableExt x ifTrue:[
                menuOrigin x:((useableExt x - w) max:0)
            ]
        ]
    ].

    "/ nice side-effect; set width first, to allow menuHeight
    "/ to be a block computing the height based upon the width.
    "/ (allows for geometry adjustments)

    menuWrapper width:w.
    h := (useableExt y - menuOrigin y - 4) min:(menuHeight value).
    menuWrapper height:h.

    menuWrapper origin:menuOrigin extent:(w@h).
    menuWrapper openModal.
! !

!ExtendedComboBox::MenuWrapper class methodsFor:'documentation'!

documentation
"
    problem: we have a grab - and get all events;
    to simulate regular behaior inside, we have to synthetically simulate
    focus control and implicit grab on buttonPress.

    [instance variables:]
        lastPointerView         <View>          view which contained the
                                                mouse pointer.
                                                used for enter/leave event generation.

        implicitGrabView         <View>         view in which button was pressed;
                                                nilled when released (wherever).
                                                If non-nil, all events are forwarded to this
                                                one (for example to scroll with mouse outside the scrollbar)

"
! !

!ExtendedComboBox::MenuWrapper methodsFor:'accessing'!

application
    "return the application, under which this view was opened,
     or nil, if there is no application
    "
    ^ comboBox application

!

clearImplicitGrab
    implicitGrabView := nil
!

preferredExtent
    "compute & return the preferredExtent from the components' preferrences
    "
    ^ (widget preferredExtent max:(widget widthOfContents @ widget heightOfContents))
      + (margin * 2) + 8.
!

widget
    "returns the widget wrapped by the menuView
    "
    ^ widget
! !

!ExtendedComboBox::MenuWrapper methodsFor:'event handling'!

dispatchEvent:event withFocusOn:focusViewOrNil delegate:doDelegate
    "dispatch the event"

    |originalEvent x y oldGrabber myGrabber p|

    (event isInputEvent not
    or:[event isPointerEnterLeaveEvent]) ifTrue:[
        super dispatchEvent:event withFocusOn:focusViewOrNil delegate:doDelegate.
      ^ self
    ].

    event isButtonPressEvent ifTrue:[
        x := event x.
        y := event y.

        ((0@0 extent:self extent) containsPoint:(x @ y)) ifFalse:[
            comboBox closeMenu.
            "/ if I took the grab from someone else, this buttonEvent should
            "/ also go to that one ...but not, if it falls into my own ext-comboBox (sigh)
            "/ (example: a sub-ext-box in an ext-box, clicking on the outer boxes menuButton)
            (oldGrabber := device activePointerGrab) notNil ifTrue:[
                p := device translatePoint:(x@y) from:(self id) to:(comboBox id).
                ((0@0 extent:comboBox extent) containsPoint:p) ifFalse:[        
                    p := device translatePoint:(x@y) from:(self id) to:(oldGrabber id).
                    event view:oldGrabber.
                    event x:p x.
                    event y:p y.

                    "/ this is a hack
                    oldGrabber class == self class ifTrue:[
                        oldGrabber clearImplicitGrab.
                    ].
                    oldGrabber dispatchEvent:event withFocusOn:focusViewOrNil delegate:doDelegate.
                ].
            ].
            ^  self.
        ]
    ].
    self forwardEvent:event withFocusOn:focusViewOrNil.
!

forwardEvent:ev withFocusOn:focusView
    "handle a key press event
    "
    |view x y p syntheticEvent|

    "/ situation: we get a buttonPress, set implicitGrab (for scrollbars etc.)
    "/ but never get the buttonRelease, since someone else (a popUp) grabbed the
    "/ pointer in the meantime, and has eaten the release event ... (double-sigh)
    implicitGrabView notNil ifTrue:[
        self sensor leftButtonPressed ifFalse:[
"/            Transcript showCR:'clear'.
            implicitGrabView := nil.
        ].
    ].

    ((x := ev x) isNil or:[(y := ev y) isNil]) ifTrue:[
        ^ self
    ].

    implicitGrabView notNil ifTrue:[
        ev isButtonEvent ifTrue:[
            p := device translatePoint:(x@y) from:(self id) to:(implicitGrabView id).
            ev view:implicitGrabView.
            ev arguments at:2 put:p x.
            ev arguments at:3 put:p y.
            implicitGrabView dispatchEvent:ev withFocusOn:focusView delegate:false.

            ev isButtonReleaseEvent ifTrue:[
                implicitGrabView := nil.
            ].
            ^ self
        ]
    ].

    view := self detectViewAtX:x y:y.
    view isNil ifTrue:[
        ^ super dispatchEvent:ev withFocusOn:focusView delegate:false
    ].

    p := device translatePoint:(x@y) from:(self id) to:(view id).

    ev isButtonPressEvent ifTrue:[
        (view wantsFocusWithButtonPress) ifTrue:[
            view requestFocus.
        ].
        view ~~ self ifTrue:[ "/ can this ever be self ?
            implicitGrabView := view.
        ]
    ].

    ev isButtonMotionEvent ifTrue:[
        lastPointerView ~~ view ifTrue:[
            "/ must generate enter/leave ... (sigh)
            lastPointerView notNil ifTrue:[
                syntheticEvent := WindowEvent inputEvent
                                     for:lastPointerView
                                     type:#pointerLeave:
                                     arguments:(Array with:0).   "/ XXX: should be fixed
                lastPointerView dispatchEvent:syntheticEvent withFocusOn:nil delegate:false.
            ].
            view notNil ifTrue:[
                syntheticEvent := WindowEvent inputEvent
                                     for:view
                                     type:#pointerEnter:x:y:
                                     arguments:(Array with:0 with:x with:y).
                view dispatchEvent:syntheticEvent withFocusOn:nil delegate:false.
            ].
            lastPointerView := view.
        ].
    ].

    ev view:view.
    ev x:p x.
    ev y:p y.
    view dispatchEvent:ev withFocusOn:focusView delegate:false.
! !

!ExtendedComboBox::MenuWrapper methodsFor:'focus handling'!

wantsFocusWithButtonPress
    "views which do not like to take the keyboard focus
     with buttonPress can do so by redefining this
     to return false"

    ^ false


! !

!ExtendedComboBox::MenuWrapper methodsFor:'initialization'!

for:aWidget in:aReceiver 
    "create a wrapper for a widget and the receiver, an extented comboBox
    "
    |hasScr|

    comboBox := aReceiver.
    widget   := aWidget.
    hasScr   := aWidget isScrollWrapper.

    comboBox menuIsScrollable ifTrue:[
        hasScr ifFalse:[
            widget := ScrollableView forView:aWidget.
            hasScr := true.
        ].
        widget horizontalScrollable:(comboBox hasHorizontalScrollBar).
        widget   verticalScrollable:(comboBox hasVerticalScrollBar).
        widget       horizontalMini:(comboBox miniScrollerHorizontal).
        widget         verticalMini:(comboBox miniScrollerVertical).
        widget   autoHideScrollBars:(comboBox autoHideScrollBars).
    ].
    widget origin:0.0 @ 0.0 corner:1.0 @ 1.0.
    self add:widget.

    hasScr ifTrue:[
        widget level:0.
        widget := widget scrolledView.
    ].    
!

hideRequest
    "hide request from windowGroup (i.e. via Escape key).
     Can be redefined in subclasses which dont like this"

    comboBox closeMenu

!

initialize
    super initialize.
    super level:0.
    super borderWidth:1.
!

level:aNumber
!

mapped
    "grab resources (mouse and keyboard)
    "
    super mapped.
    self assignKeyboardFocusToFirstInputField.
    widget notNil ifTrue:[widget level:0].
! !

!ExtendedComboBox::MenuWrapper methodsFor:'queries'!

raiseDeiconified
    ^ self raise

!

type
    ^ nil.


! !

!ExtendedComboBox::MenuWrapper methodsFor:'searching'!

detectViewAtX:x y:y
    "detect view at x@y. if no view is detected or
     the it is my view nil is returned
    "
    |v|

    (x notNil or:[y notNil]) ifTrue:[
        ((x between:0 and:width) and:[y between:0 and:height]) ifTrue:[
            v := self detectViewAtX:x y:y in:self.
            v ~~ self ifTrue:[^ v].
        ]
    ].
    ^ nil
!

detectViewAtX:x y:y in:aTopView
    "detect view at x@y in a top view; if no view is detected
     the topview is returned
    "
    |subviews|

    (subviews := aTopView subViews) notNil ifTrue:[
        subviews do:[:v||p|
            (    (x between:(v left) and:(v right))
             and:[y between:(v top)  and:(v bottom)]
            ) ifTrue:[
                p := device translatePoint:(x@y) from:(aTopView id) to:(v id).
                ^ self detectViewAtX:p x y:p y in:v.
            ]
        ]
    ].
    ^ aTopView
! !

!ExtendedComboBox class methodsFor:'documentation'!

version
    ^ '$Header: /cvs/stx/stx/libwidg2/ExtendedComboBox.st,v 1.25 2000-10-15 14:06:07 ca Exp $'
! !