DSVColumnView.st
author tm
Fri, 04 Jul 2003 10:05:19 +0200
changeset 2535 4df3f0a09b83
parent 2524 a41c43dba6c9
child 2537 cf1b9b6eeb41
permissions -rw-r--r--
fixed: characterPressed: ... on non editable column

"
 COPYRIGHT (c) 1997 by Claus Gittinger / 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:#DSVColumnView
	instanceVariableNames:'labelView listHolder editValue editView multipleSelectOk useIndex
		selectedColIndex selectedRowIndex selectRowOnDefault
		buttonMotionAction buttonReleaseAction rowHeight
		columnDescriptors viewOrigin colorMap rowFontAscent lockRowIndex
		rowIfAbsentBlock columnHolder registererImages list fgColor
		separatorSize catchChangeEvents beDependentOfRows bgColor
		actionBlock builder tabIntern doubleClickActionBlock
		verticalSpacing horizontalSpacing rowSelectorForm
		buttonLightColor buttonShadowColor buttonHalfLightColor
		buttonHalfShadowColor checkToggleExtent checkToggleForm
		checkToggleActiveImage checkTogglePassiveImage checkToggleLevel
		comboButtonExtent comboButtonForm comboButtonLevel dropTarget
		dropSource columnAdaptor tabAtEndAction tabAtStartAction
		modifiedChannel autoScroll autoScrollBlock needFitColumns
		scrollWhenUpdating selectionForegroundColor
		selectionBackgroundColor previousExtent'
	classVariableNames:'DefaultForegroundColor DefaultBackgroundColor
		DefaultHilightForegroundColor DefaultHilightBackgroundColor
		ButtonLightColor ButtonShadowColor CheckToggleActiveImage
		CheckTogglePassiveImage ButtonHalfLightColor
		ButtonHalfShadowColor ButtonEdgeStyle CheckToggleForm
		CheckToggleLevel CheckToggleExtent ComboButtonForm
		ComboButtonLevel ComboButtonExtent'
	poolDictionaries:''
	category:'Views-DataSet'
!

!DSVColumnView class methodsFor:'documentation'!

copyright
"
 COPYRIGHT (c) 1997 by Claus Gittinger / 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
"
    implements a scrollable selection view based on rows and columns

    [Instance variables:]

        editValue               <Model>                 current editing model
        editView                <View>                  current editing component

        buttonReleaseAction     <Action or nil>         called if the mouse button is released
        buttonMotionAction      <Action or nil>         called during mouse motion with one
                                                        argument the point under the mouse.

        multipleSelectOk        <Boolean>               multiple selection enabled/disabled

        selectedColIndex        <Integer>               selected column index or 0
        selectedRowIndex        <Integer>               selected row    index or 0

        rowHeight               <Integer>               maximum height of any row

        columnDescriptors       <SequancableCollection> list of column descriptors

        viewOrigin              <Point>                 current view origin

        colorMap                <Dictionary>            store and register used colors on device

        rowFontAscent           <SmallInteger>          inset of a printable text in a row
                                                        including separator and font ascent.

        lockRowIndex            <SmallInteger>          internal used to indicate a row which has
                                                        changed its contents but no redraw should be
                                                        done( at:put: ).

        columnHolder            <ValueHolder>           holder which keeps the list of column descriptors.

        registererImages        <IdentityDictionary>    list of images registered on the device

        list                    <SequancableCollection> list of rows


        catchChangeEvents       <Boolean>               internal used to discard change notifications

        beDependentOfRows       <Boolean>               keep rows dependent; on default is disabled.
                                                        in case of enabled a row can raise a change
                                                        notification whithout a parameter which
                                                        will force a redraw of the row or the
                                                        readSelector of the column which will
                                                        redraw the cell in the row only.

        fgColor                 <Color>                 foreground color
        bgColor                 <Color>                 background color
        hgLgFgColor             <Color>                 highlight foreground color (selected)
        hgLgBgColor             <Color>                 highlight background color (selected)

        buttonLightColor        <Color>                 LightColor      ( drawing the edge of a button )
        buttonShadowColor       <Color>                 ShadowColor     ( drawing the edge of a button )
        buttonHalfLightColor    <Color>                 HalfLightColor  ( drawing the edge of a button )
        buttonHalfShadowColor   <Color>                 HalfShadowColor ( drawing the edge of a button )

        actionBlock             <a OneArgBlock>         action block performed on select
        doubleClickActionBlock  <a OneArgBlock>         action block performed on double click
        rowIfAbsentBlock        <a OneArgBlock>         this block is performed on an emty list entry
                                                        to retrive the item from the application. The
                                                        argument to the block is the index into the list.
                                                        The block should return the row instance which
                                                        is put to the list under the index.

        builder                 <UIBuilder>             builder set by application

        verticalSpacing         <SmallInteger>          vertical   row spacing( top  & bottom )
        horizontalSpacing       <SmallInteger>          horizontal row spacing( left & right )
        separatorSize           <SmallInteger>          line width of a vertical or horizontal separator

        rowSelectorForm         <Form>                  form used by a row selector

        checkToggleForm         <Form>                  form used by a checkToggle
        checkToggleExtent       <Point>                 extent of a checkToggle
        checkToggleLevel        <SmallInteger>          level used to draw a check toggle

        comboButtonForm         <Form>                  form used by a comboList or -Box
        comboButtonExtent       <Point>                 extent of a comboList or -Box
        comboButtonLevel        <SmallInteger>          level used to draw a comboList or -Box

        dropTarget              <DropTarget>            drag & drop target
        dropSource              <DropSource>            drag & drop source

    [author:]
        Claus Atzkern

    [see also:]
        DataSetColumnSpec
        DataSetColumn
        DataSetView
"
! !

!DSVColumnView class methodsFor:'accessing forms'!

rowSelector
    "This resource specification was automatically generated
     by the ImageEditor of ST/X."

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

    "
     self rowSelector inspect
     ImageEditor openOnClass:self andSelector:#rowSelector
    "

    <resource: #image>

    ^Icon
        constantNamed:#'DSVColumnView rowSelector'
        ifAbsentPut:[(Depth2Image new) width: 11; height: 11; photometric:(#palette); bitsPerSample:(#(2 )); samplesPerPixel:(1); bits:(ByteArray fromPackedString:'@@@@@A@@@AP@@A4@EW=@G?? O?:@@C(@@B @@B@@@@@@') ; colorMapFromArray:#[0 0 0 255 255 255 127 127 127 170 170 170]; mask:((Depth1Image new) width: 11; height: 11; photometric:(#blackIs0); bitsPerSample:(#(1 )); samplesPerPixel:(1); bits:(ByteArray fromPackedString:'C@@N@@<@?8C?0O? ?<C? @<@C @L@@@a') ; yourself); yourself]


! !

!DSVColumnView class methodsFor:'defaults'!

defaultFont
    ^ SelectionInListView defaultFont
!

horizontalSpacing
    "returns the default horizontal space between rows
    "
    ^ 4

!

updateStyleCache
    "extract values from the styleSheet and cache them in class variables
    "
    <resource: #style (#textForegroundColor 
                       #'scrollableView.backgroundColor'
                       #'selection.hilightForegroundColor'
                       #'selection.hilightBackgroundColor'   )>

    DefaultForegroundColor        := StyleSheet colorAt:#'textForegroundColor' default:(Color black).
    DefaultBackgroundColor        := StyleSheet colorAt:#'scrollableView.backgroundColor' default:DefaultViewBackgroundColor.

    DefaultHilightForegroundColor := StyleSheet colorAt:#'selection.hilightForegroundColor' default:DefaultBackgroundColor.
    DefaultHilightBackgroundColor := StyleSheet colorAt:#'selection.hilightBackgroundColor' default:DefaultForegroundColor.

    DefaultHilightForegroundColor = DefaultHilightBackgroundColor ifTrue:[
        DefaultHilightBackgroundColor := Color black
    ].
    ButtonLightColor       := StyleSheet colorAt:#'button.lightColor'.
    ButtonShadowColor      := StyleSheet colorAt:#'button.shadowColor'.
    ButtonHalfLightColor   := StyleSheet colorAt:#'button.halfLightColor'.
    ButtonHalfShadowColor  := StyleSheet colorAt:#'button.halfShadowColor'.
    ButtonEdgeStyle        := StyleSheet at:#'button.edgeStyle'.
    CheckToggleActiveImage := StyleSheet at:#'checkToggle.activeImage'.

    CheckToggleActiveImage isNil ifTrue:[
        CheckTogglePassiveImage := nil
    ] ifFalse:[
        CheckTogglePassiveImage := StyleSheet at:#'checkToggle.passiveImage'.

        CheckTogglePassiveImage isNil ifTrue:[
            CheckToggleActiveImage := nil
        ]
    ].
    ComboButtonForm   := nil.
    ComboButtonLevel  := nil.
    ComboButtonExtent := nil.

    CheckToggleForm   := nil.
    CheckToggleLevel  := nil.
    CheckToggleExtent := nil.

"
self updateStyleCache.
"

    "Modified: / 26.10.1997 / 17:09:07 / cg"
!

verticalSpacing
    "returns the default vertical space between rows
    "
    ^ 2

! !

!DSVColumnView class methodsFor:'resources'!

dragIconMulti
    "This resource specification was automatically generated
     by the ImageEditor of ST/X."

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

    "
     self dragIconMulti inspect
     ImageEditor openOnClass:self andSelector:#dragIconMulti
    "

    <resource: #image>

    ^Icon
        constantNamed:#'DSVColumnView dragIconMulti'
        ifAbsentPut:[(Depth1Image new) width: 32; height: 32; photometric:(#palette); bitsPerSample:(#(1 )); samplesPerPixel:(1); bits:(ByteArray fromPackedString:'@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@O?<0@C??L@@<@@@@O@@@@C3??L@<??3@OO?<<C3??O@<??0@OO?<@C3???0<???<OO???C3???0<???<OO???@C???0@???<@O???@C???0@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@b') ; colorMapFromArray:#[0 0 0 255 255 255]; mask:((ImageMask new) width: 32; height: 32; photometric:(#blackIs0); bitsPerSample:(#(1 )); samplesPerPixel:(1); bits:(ByteArray fromPackedString:'@@@@@@@@@@@@@@@@@@@@@O??<@C???@@???<@O???@C????@????0O????C????0?????O????3?????????????????????????????????????????????????????@????0O???<C????@????0@@@@@@@@@@@@@@@@@@@@@b') ; yourself); yourself]
!

dragIconSingle
    "This resource specification was automatically generated
     by the ImageEditor of ST/X."

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

    "
     self dragIconSingle inspect
     ImageEditor openOnClass:self andSelector:#dragIconSingle
    "

    <resource: #image>

    ^Icon
        constantNamed:#'DSVColumnView dragIconSingle'
        ifAbsentPut:[(Depth1Image new) width: 32; height: 32; photometric:(#palette); bitsPerSample:(#(1 )); samplesPerPixel:(1); bits:(ByteArray fromPackedString:'@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@O?<0@C??L@@??30@O?<<@C??@@@??0@@O???@C???0@???<@O???@C???0@???<@O???@C???0@???<@O???@C???0@???<@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@b') ; colorMapFromArray:#[0 0 0 255 255 255]; mask:((ImageMask new) width: 32; height: 32; photometric:(#blackIs0); bitsPerSample:(#(1 )); samplesPerPixel:(1); bits:(ByteArray fromPackedString:'@@@@@@@@@@@@@@@@@@@@@O??<@C???@@???<@O???@C???<@????@O???<C????@????0O???<C????@????0O???<C????@????0O???<C????@????0O???<C????@????0O???<@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@b') ; yourself); yourself]
! !

!DSVColumnView methodsFor:'accessing'!

builder
    "get the builder (UIBuilder or nil)
    "
    ^ builder
!

builder:aBuilder
    "set the builder (UIBuilder or nil)
    "
    builder := aBuilder
!

columnView
    "returns self
    "
    ^ self
!

labelView:aView
    labelView := aView for:self.

    labelView layout:(LayoutFrame
                        leftFraction:0 offset:0 
                        rightFraction:1 offset:0 
                        topFraction:0 offset:0 
                        bottomFraction:0 offset:[self preferredLabelViewHeight]).
!

level:aLevel
    "change the level and thus the level of the labelView
    "
    aLevel ~~ level ifTrue:[
        super level:aLevel.
"/        labelView level:aLevel.
    ]
!

modifiedChannel
    "return the value of the instance variable 'modifiedChannel' (automatically generated)"

    ^ modifiedChannel

    "Created: / 30.1.2000 / 12:10:57 / cg"
!

modifiedChannel:something
    "set the value of the instance variable 'modifiedChannel' (automatically generated)"

    modifiedChannel := something.

    "Created: / 30.1.2000 / 12:10:57 / cg"
!

preferredLabelViewHeight
    ^ labelView preferredHeight + (labelView margin + self verticalSpacing * 2).
!

rowFontAscent
    "returns the inset of a printable text in a row
    "
    ^ rowFontAscent
! !

!DSVColumnView methodsFor:'accessing-actions'!

action:aOneArgAction
    "set the action block to be performed on select
    "
    actionBlock := aOneArgAction


!

doubleClickAction:aOneArgAction
    "set the action block to be performed on doubleclick
    "
    doubleClickActionBlock := aOneArgAction


!

rowIfAbsent:aOneArgAction
    "set the action block to be performed on each 'nil' entry into the
     list. The argument to the block is the index into the list. The
     block returns the row which is put to the list
    "
    rowIfAbsentBlock := aOneArgAction


!

tabAtEndAction:aNoneArgAction
    "set the action, called without any argument at end of the list entering
     tab next.
     The default is to give the focus to the view after self in the focusSequence
    "
    tabAtEndAction := aNoneArgAction


!

tabAtStartAction:aNoneArgAction
    "set the action, called without any argument at start of the list entering
     tab previous.
     The default is to give the focus to the view before self in the focusSequence
    "
    tabAtStartAction := aNoneArgAction


! !

!DSVColumnView methodsFor:'accessing-behavior'!

beDependentOfRows
    "make myself dependent of any row; in this case any change notification
     raised by a row is catched and the cell identified by the 'readSelector'
     is redrawn. In case of a nil readSelector, the whole raw is redrawn.
        -> row changed:'what'
     On default the attribute is set to false (disabled).
    "
    ^ beDependentOfRows
!

beDependentOfRows:aBool
    "make myself dependent of any row; in this case any change notification
     raised by a row is catched and the cell identified by the 'readSelector'
     is redrawn. In case of a nil readSelector, the whole raw is redrawn.
        -> row changed:'what'
     On default the attribute is set to false (disabled).
    "
    aBool ~~ beDependentOfRows ifTrue:[
        beDependentOfRows := aBool.

        list size ~~ 0 ifTrue:[
            list do:[:aRow| aRow notNil ifTrue:[
                beDependentOfRows ifTrue:[aRow addDependent:self]
                                 ifFalse:[aRow removeDependent:self]
                ]
            ]
        ]
    ]
!

enableChannel:aChannel
    enableChannel notNil ifTrue:[
        enableChannel removeDependent:self
    ].
    (enableChannel := aChannel) notNil ifTrue:[
        enableChannel addDependent:self
    ].
!

multipleSelectOk
    "allow/disallow multiple row selections; the default is false
    "
    ^ multipleSelectOk
!

multipleSelectOk:aState
    "allow/disallow multiple row selections; the default is false
    "
    aState == multipleSelectOk ifFalse:[
        multipleSelectOk := aState.
        self deselect
    ]
!

opaqueColumnResize
    ^ labelView opaqueColumnResize
!

opaqueColumnResize:aBoolean
    labelView opaqueColumnResize:aBoolean
!

scrollWhenUpdating
    "return the scroll behavior, when I get a new text 
     (via the model or the #contents/#list)
     Possible returnValues are:
        #keep / nil     -> no change
        #endOfText      -> scroll to the end
        #beginOfText    -> scroll to the top
     The default is #beginOfText.
     This may be useful for fields which get new values assigned from
     the program (i.e. not from the user)"

    ^ scrollWhenUpdating
!

scrollWhenUpdating:aSymbolOrNil
    "define how to scroll, when I get a new text 
     (via the model or the #contents/#list)
     Allowed arguments are:
        #keep / nil     -> no change
        #endOfText      -> scroll to the end
        #beginOfText    -> scroll to the top
     The default is #beginOfText.
     This may be useful for fields which get new values assigned from
     the program (i.e. not from the user)"

    scrollWhenUpdating := aSymbolOrNil
!

selectRowOnDefault
    "in case of selecting a none selectable cell, the row is selected
    "
    ^ selectRowOnDefault
!

selectRowOnDefault:aBool
    "in case of selecting a none selectable cell, the row is selected
    "
    selectRowOnDefault := aBool
!

tabIntern
    "returns true if tabing is supported in the widget
    "
    ^ tabIntern
!

tabIntern:aBool
    "returns true if tabing is supported in the widget
    "
    tabIntern := aBool ? true
!

useIndex
    "specify, if the selected components value or its index in the
     list should be sent to the model. The default is its index.
    "
    ^ useIndex


!

useIndex:aBool
    "specify, if the selected components value or its index in the
     list should be sent to the model. The default is its index.
    "
    useIndex := aBool

! !

!DSVColumnView methodsFor:'accessing-colors'!

backgroundColor
    "get the background color of the rows
    "
    ^ bgColor


!

backgroundColor:aColor
    "set the background color of the rows
    "
    bgColor ~~ aColor ifTrue:[
        super viewBackground:bgColor.

        self realized ifTrue:[
            bgColor := aColor onDevice:device.
            self invalidate
        ] ifFalse:[
            bgColor := aColor
        ]
    ]
!

foregroundColor
    "return the foreground color of the rows
    "
    ^ fgColor

!

foregroundColor:aColor
    "set the foreground color of the rows
    "
    fgColor ~~ aColor ifTrue:[
        self realized ifTrue:[
            fgColor := aColor onDevice:device.
            self invalidate
        ] ifFalse:[
            fgColor := aColor
        ]
    ]

!

selectionBackgroundColor
    "returns the background color of a selected row
    "
    ^ selectionBackgroundColor
!

selectionForegroundColor
    "returns the foreground color of a selected row
    "
    ^ selectionForegroundColor
!

separatorDarkColor
    "returns the dark color used for drawing a shadowed separator (3D)
    "
    ^ shadowColor


!

separatorLightColor
    "returns the light color used for drawing a shadowed separator (3D)
    "
    ^ lightColor


! !

!DSVColumnView methodsFor:'accessing-columns'!

columnAt:anIndex
    "returns the column at an index
    "
    ^ columnDescriptors at:anIndex ifAbsent:nil
!

columnDescriptors
    "returns list of column descriptors
    "
    ^ columnDescriptors collect:[:aCol| aCol description ]
!

columnDescriptors:aColumnDescriptionList
    "set the columnDescriptors; scroll to top and deselect
    "
    | delta|

    self deselect.

    (viewOrigin x ~~ 0 or:[viewOrigin y ~~ 0]) ifTrue:[
        delta := viewOrigin negated.
        viewOrigin := 0@0.
        self originChanged:delta
    ].
    self setColumnDescriptors:aColumnDescriptionList
!

firstColumn
    "returns the first column
    "
    ^ columnDescriptors at:1

!

lastColumn
    "returns the last column
    "
    ^ columnDescriptors last

!

setColumnDescriptors:aColumnDescriptionList
    "set the columnDescriptors; dont deselect and do not scroll to top
    "
    |cid|

    cid := 0.
    columnDescriptors := aColumnDescriptionList ? #().

    columnDescriptors := columnDescriptors collect:[:el||dsc lbl|
        dsc := el isSequenceable ifTrue:[DataSetColumnSpec new fromLiteralArrayEncoding:el]
                                ifFalse:[el].
        cid := cid + 1.
        lbl := DataSetLabel new description:dsc builder:builder on:labelView.
        DataSetColumn new on:self description:dsc columnNumber:cid label:lbl
    ].

    preferredExtent := nil.
    labelView columns:columnDescriptors.

    shown ifTrue:[
        self fitColumns.
    ].
! !

!DSVColumnView methodsFor:'accessing-interactors'!

checkToggleActiveImage
    ^ checkToggleActiveImage
!

checkToggleExtent
    "returns the extent of a checkToggle
    "
    ^ checkToggleExtent
!

checkToggleForm
    "returns the form of a checkToggle
    "
    ^ checkToggleForm
!

checkToggleLevel
    "returns the level of a checkToggle button
    "
    ^ checkToggleLevel
!

checkTogglePassiveImage
    ^ checkTogglePassiveImage
!

comboButtonExtent
    "returns the extent of a comboList or -Box
    "
    ^ comboButtonExtent
!

comboButtonForm
    "returns the form of a comboList or -Box
    "
    ^ comboButtonForm
!

comboButtonLevel
    "returns the level of a comboList or -Box button
    "
    ^ comboButtonLevel
!

rowSelectorExtent
    "returns the bitmap of a selected row
    "
    ^ rowSelectorForm extent
!

rowSelectorForm
    "returns the bitmap of a selected row
    "
    ^ rowSelectorForm
! !

!DSVColumnView methodsFor:'accessing-mvc'!

columnAdaptor
    "return the value of the instance variable 'columnAdaptor' (automatically generated)"

    columnAdaptor isValueModel ifTrue:[^ columnAdaptor value].
  ^ columnAdaptor
!

columnAdaptor:something
    "set the value of the instance variable 'columnAdaptor' (automatically generated)"

    columnAdaptor isValueModel ifTrue:[
        columnAdaptor removeDependent:self
    ].
    (columnAdaptor := something) isValueModel ifTrue:[
        columnAdaptor addDependent:self
    ].
!

columnHolder
    "get the valueHolder, which keeps the list of column descriptions
    "
    ^ columnHolder

!

columnHolder:aValueHolder
    "set the valueHolder, which keeps the list of column descriptions
    "
    |columns|

    columnHolder notNil ifTrue:[
        columnHolder removeDependent:self
    ].

    (columnHolder := aValueHolder) notNil ifTrue:[
        columnHolder addDependent:self.
        columns := columnHolder value.

        columns notNil ifTrue:[
            self columnDescriptors:columns
        ]
    ].

!

listAt:index put:newElement
    "kludge callback, when an element hs to be replaced
     due to a col-adaptor returning a new row element
    "
    |list|

    (list := listHolder value) notNil ifTrue:[
        list at:index put:newElement
    ]
!

listHolder
    "get the valueHolder which holds the list of rows
    "
    ^ listHolder
!

listHolder:aListHolder
    "set the valueHolder which holds the list of rows
    "
    listHolder ~~ aListHolder ifTrue:[
        listHolder notNil ifTrue:[
            listHolder removeDependent:self
        ].

        (listHolder := aListHolder) notNil ifTrue:[
            listHolder addDependent:self
        ]
    ].
    self pushEvent:#list: with:(listHolder value).

!

model:aModel
    "set the valueHolder which holds the selection and maybe the list of rows
    "
    model notNil ifTrue:[
        model removeDependent:self.
        (model respondsTo:#list) ifTrue:[
            (model list == listHolder) ifTrue:[
                self listHolder:nil
            ]
        ]
    ].

    (model := aModel) notNil ifTrue:[
        model addDependent:self.

        (aModel respondsTo:#list) ifTrue:[
            self listHolder:model list
        ]
    ]

! !

!DSVColumnView methodsFor:'accessing-rows'!

at:aRowNr
    "return the row at an index, aRowNr
    "
    |row|

    (row := list at:aRowNr) isNil ifTrue:[
        lockRowIndex := aRowNr.

        (rowIfAbsentBlock notNil
        and:[(row := rowIfAbsentBlock value:aRowNr) notNil]) ifTrue:[
            list at:aRowNr put:row.
            beDependentOfRows ifTrue:[row addDependent:self].
        ].

        lockRowIndex := 0
    ].
    ^ row

    "Modified: / 31.7.1998 / 01:07:46 / cg"
!

at:aRowNr ifAbsent:exceptionBlock
    "return the row at a aRowNr. If the index is invalid, return the
     result of evaluating the exceptionblock
    "
    (aRowNr between:1 and:list size) ifTrue:[
        ^ self at:aRowNr
    ].
    ^ exceptionBlock value
!

at:aRowNr put:aRow
    "change the row at an index. The added row is returned
    "
    lockRowIndex ~~ aRowNr ifTrue:[
        (aRowNr <= list size and:[(list at:aRowNr) == aRow]) ifTrue:[
            self invalidateRowAt:aRowNr
        ] ifFalse:[
            self replaceFrom:aRowNr to:aRowNr with:(Array with:aRow) startingAt:1.
        ]
    ].
  ^ aRow
!

first
    "return the first row
    "
    ^ self at:1
!

identityIndexOfRow:aRow
    "returns index of a row or 0
    "
    (list size ~~ 0 and:[aRow notNil]) ifTrue:[
        ^ list identityIndexOf:aRow
    ].
    ^ 0
!

last
    "return the last row
    "
    ^ self at:(list size)
!

list
    "get the list of rows
    "
    ^ list



!

list:aList
    "set the list of rows
    "
    |makeDependent selectionHasChanged|

    "/ remove selection without redraw

    selectionHasChanged := self hasSelection.

    editValue notNil ifTrue:[
        editValue removeDependent:self.
        editValue := nil
    ].
    self destroyEditView.

    selectedColIndex := 0.
    selectedRowIndex := multipleSelectOk ifTrue:[nil] ifFalse:[0].

    shown ifFalse:[
        preferredExtent := nil
    ] ifTrue:[
        aList size ~~ 0 ifTrue:[  "/ otherwise keep old values
            columnDescriptors notNil ifTrue:[
                columnDescriptors do:[:aCol| aCol invalidate ].
            ].
            preferredExtent := nil.
        ]
    ].

    (makeDependent := beDependentOfRows) ifTrue:[
        self beDependentOfRows:false.
    ].

    aList size ~~ 0 ifTrue:[
        list := OrderedCollection new:(aList size).
        aList do:[:el| list add:el ].
    ] ifFalse:[
        list := nil.
        viewOrigin := 0 @ 0.
    ].
    self beDependentOfRows:makeDependent.

    shown ifTrue:[
        self fitColumns
    ] ifFalse:[
        needFitColumns := true
    ].
    selectionHasChanged ifTrue:[
        self selectionChanged.
    ].
    self contentsChanged.
! !

!DSVColumnView methodsFor:'accessing-visibility'!

font:aFont
    "set the font for all shown rows.
    "
    (aFont notNil and:[aFont ~~ font]) ifTrue:[
        super font:(aFont onDevice:device).
        realized ifTrue:[
            columnDescriptors do:[:aCol| aCol invalidate ].
            self preferredExtentChanged.
            self invalidate.
            self contentsChanged
        ].
        labelView notNil ifTrue:[
            labelView font:font
        ].
    ]
!

has3Dseparators
    "returns true if shown in 3D mode
    "
    ^ separatorSize ~~ 1
!

has3Dseparators:aBool
    "enable or disable 3D mode
    "
    |newSepSize|

    newSepSize := aBool ifTrue:[2] ifFalse:[1].

    newSepSize ~~ separatorSize ifTrue:[
        separatorSize := newSepSize.

        realized ifTrue:[
            columnDescriptors do:[:aCol| aCol invalidate ].
            self preferredExtentChanged.
            self invalidate.
            self contentsChanged
        ]
    ]


!

horizontalSpacing
    "horizontal spacing used by columns
    "
    ^ horizontalSpacing
!

horizontalSpacing:aNumber
    "horizontal spacing used by columns
    "
    horizontalSpacing ~~ aNumber ifTrue:[
        horizontalSpacing := aNumber.
        self preferredExtentChanged.
    ].
!

showLabels
    "control the labels view to be visible or unvisible
    "
    ^ labelView isVisible

!

showLabels:aState
    "control the labels view to be visible or unvisible
    "
    labelView isVisible:aState
!

verticalSpacing
    "vertical spacing used by columns
    "
    ^ verticalSpacing
!

verticalSpacing:aNumber
    "vertical spacing used by columns
    "
    verticalSpacing ~~ aNumber ifTrue:[
        verticalSpacing := aNumber.
        self preferredExtentChanged.
    ].
! !

!DSVColumnView methodsFor:'adding & removing rows'!

add:aRow
    "insert row at end
    "
    ^ self add:aRow beforeIndex:(1 + list size)
!

add:aRow afterIndex:aRowNr
    "add a new row after slot aRowNr and redisplay; returns nil in case
     of an invalid index or the row
    "
    ^ self add:aRow beforeIndex:(aRowNr + 1)
!

add:aRow beforeIndex:aRowNr
    "add a new row before slot aRowNr and redisplay; returns nil in case
     of an invalid index or the row
    "
    self addAll:(Array with:aRow) beforeIndex:aRowNr.
    ^ aRow.
!

addAll:aList beforeIndex:start
    "add a collection of rows before slot start and redisplay
    "
    |y0 y1 yD h dH size noSel|

    (size := aList size) == 0 ifTrue:[
        ^ self
    ].

    list size == 0 ifTrue:[
        ^ self list:aList
    ].

    beDependentOfRows ifTrue:[
        aList do:[:aRow| aRow notNil ifTrue:[aRow addDependent:self]]
    ].

    noSel := self numberOfSelections.

    noSel ~~ 0 ifTrue:[
        multipleSelectOk ifFalse:[
            selectedRowIndex >= start ifTrue:[
                selectedRowIndex := selectedRowIndex + size
            ]
        ] ifTrue:[
            1 to:noSel do:[:i||v|
                (v := selectedRowIndex at:i) >= start ifTrue:[
                    selectedRowIndex at:i put:(v + size)
                ]
            ]
        ]
    ].
    list addAll:aList beforeIndex:start.
    self recomputeHeightOfContents.

    y0 := (start - 1) * rowHeight.
    yD := size * rowHeight.
    y1 := y0 + yD.

    y0 < viewOrigin y ifTrue:[
        self originWillChange.
        viewOrigin := viewOrigin x @ (yD + viewOrigin y).
        "/ viewOrigin y:(yD + viewOrigin y).
        self originChanged:(0 @ yD).
    ].

    (shown not or:[self sensor hasDamageFor:self]) ifTrue:[
        self invalidate.
        self contentsChanged.
        ^ self
    ].

    y0 := self yVisibleOfRowNr:start.
    h  := height - margin.
    y1 := y0 + yD min:h.

    (y1 > margin and:[y0 < h]) ifTrue:[
        "/ cg: if I have a non-solid background color,
        "/ or individual items have a bgColor selector,
        "/ invalidate the area (and readraw) instead of a scroll.
        (viewBackground isImageOrForm 
        or:[ self anyColumnHasPotentialNonConstantBackground ])
        ifTrue:[
            "/ do not scroll but invalidate ...
            self invalidateX:margin y:y0 width:width - margin - margin height:(height - y0).
        ] ifFalse:[
            h  := h - y1.
            y0 := y0 max:margin.
            dH := y1 - y0.

            start == list size ifFalse:[
                self copyFrom:self x:0 y:y0 toX:0 y:y1 width:width height:h async:false
            ].
            self invalidateX:margin y:y0 width:width - margin - margin height:dH.
        ]
    ].
    self contentsChanged.
!

addFirst:aRow
    "insert a row at start
    "
    ^ self add:aRow beforeIndex:1
!

remove:aRow
    "remove a row
    "
    |idx|

    idx := list identityIndexOf:aRow.

    idx ~~ 0 ifTrue:[
        self removeFrom:idx to:idx.
    ].
    ^ aRow
!

removeFirst
    "remove first row; returns the removed row
    "
    ^ self removeIndex:1
!

removeFrom:startIndex to:stopIndex
    "remove rows from start to stop
    "
    |coll noRedraw
     noSel "{ Class: SmallInteger }"
     size  "{ Class: SmallInteger }"
     start "{ Class: SmallInteger }"
     stop  "{ Class: SmallInteger }"
     y0    "{ Class: SmallInteger }"
     y1    "{ Class: SmallInteger }"
     oY    "{ Class: SmallInteger }"
     dY    "{ Class: SmallInteger }"
     yB    "{ Class: SmallInteger }"
     h     "{ Class: SmallInteger }"
    |

    (    (start := startIndex) < 1
     or:[(stop := stopIndex) > list size]
    ) ifTrue:[
        ^ self subscriptBoundsError:start
    ].
    size := stop - start + 1.

    beDependentOfRows ifTrue:[
        list from:start to:stop do:[:r| r notNil ifTrue:[r removeDependent:self]].
    ].
    noSel := self numberOfSelections.

    noSel ~~ 0 ifTrue:[
        noSel == 1 ifTrue:[
            noSel := self firstIndexSelected.

            noSel < start ifFalse:[
                noSel > stop ifTrue:[
                    noSel := noSel - size.

                    multipleSelectOk ifFalse:[selectedRowIndex := noSel]
                                      ifTrue:[selectedRowIndex at:1 put:noSel]
                ] ifFalse:[
                    editValue notNil ifTrue:[
                        editValue removeDependent:self.
                        editValue := nil.
                    ].
                    self deselect.
                ]
            ]
        ] ifFalse:[
            coll := OrderedCollection new:noSel.

            selectedRowIndex do:[:i|
                i < start ifTrue:[
                    coll add:i
                ] ifFalse:[
                    i > stop ifTrue:[
                        coll add:(i - size)
                    ]
                ]
            ].
            coll size == 0 ifTrue:[
                self deselect
            ] ifFalse:[
                selectedRowIndex := coll
            ]
        ]
    ].
    list removeFrom:start to:stop.

    y0 := start - 1 * rowHeight.
    dY := size * rowHeight.
    y1 := dY + y0.
    yB := y1 + margin - viewOrigin y.
    self recomputeHeightOfContents.

    y0 < (oY := viewOrigin y) ifTrue:[
        (noRedraw := y1 <= oY) ifFalse:[dY := y0 - oY]
                                ifTrue:[dY := dY negated].
        self originWillChange.
        viewOrigin := viewOrigin x @ (dY + oY).
        "/ viewOrigin y:(dY + oY).
        self originChanged:(0 @ dY).        
    ] ifFalse:[
        noRedraw := y0 > (height + viewOrigin y)
    ].

    (shown not or:[self sensor hasDamageFor:self]) ifTrue:[
          self invalidate.
        ^ self contentsChanged.
    ].

    (noRedraw or:[shown not]) ifFalse:[
        y0 := self yVisibleOfRowNr:start.
        y0 := y0 max:margin.

        "/ cg: if I have a non-solid background color,
        "/ or individual items have a bgColor selector,
        "/ invalidate the area (and readraw) instead of a scroll.
        (viewBackground isImageOrForm 
        or:[ self anyColumnHasPotentialNonConstantBackground ])
        ifTrue:[
            "/ do not scroll but invalidate ...
        ] ifFalse:[
            y1 := yB.
            h  := height - margin - yB.

            h > 0 ifTrue:[
                self copyFrom:self x:0 y:yB toX:0 y:y0 width:width height:h async:false
            ].
            y0 := y0 + h.
        ].
        self invalidateX:margin y:y0 width:width - margin - margin height:(height - y0).
    ].
    self contentsChanged.
!

removeIndex:aRowNr
    "remove row at an index; returns the removed row
    "
    |row|

    row := list at:aRowNr ifAbsent:nil.
    self removeFrom:aRowNr to:aRowNr.
  ^ row
!

removeLast
    "remove last row; the row is returned
    "
    ^ self removeIndex:(list size)
!

replaceFrom:start to:stop with:aCollection startingAt:repStart
    "replace elements in the receiver between index start and stop,
     with elements  taken from replacementCollection starting at repStart.
     Return the receiver.
    "
    |inSelList listSize repStop run|

    inSelList := OrderedCollection new.
    listSize  := list size.
    repStop   := repStart + (stop - start).

    list isNil ifTrue:[
        list := OrderedCollection new
    ].

    [listSize < stop] whileTrue:[ list add:nil. listSize := listSize + 1 ].

    start to:stop do:[:i| |aRow|
        aRow := list at:i ifAbsent:nil.
    
        aRow notNil ifTrue:[
            beDependentOfRows ifTrue:[aRow removeDependent:self].

            (self isInSelection:i) ifTrue:[
                inSelList add:i
            ].
            list at:i put:nil.
        ]
    ].

    beDependentOfRows ifTrue:[
        aCollection from:repStart to:repStop do:[:aRow|
            aRow notNil ifTrue:[aRow addDependent:self]
        ]
    ].

    run := start.
    aCollection from:repStart to:repStop do:[:aRow|
        list at:run put:aRow.
        run := run + 1.
    ].
    self invalidateRowsFrom:start to:stop.

    inSelList size ~~ 0 ifTrue:[
        self numberOfSelections == inSelList size ifTrue:[
            self deselect
        ] ifFalse:[
            selectedRowIndex := selectedRowIndex select:[:i| (inSelList identityIndexOf:i) == 0 ].
            self selectionChanged.
        ]
    ].
! !

!DSVColumnView methodsFor:'change & update'!

changeWidthOfColumn:aColumn deltaX:aDeltaX

    aColumn setDescWidth:(aColumn width + aDeltaX).
    preferredExtent notNil ifTrue:[self fitColumns]
!

update:what with:aPara from:chgObj
    "one of my rows/cells changed its value
    "
    |row newValue realValue listHoldersList arg1 arg2 col idx|

    chgObj == columnHolder ifTrue:[
        ^ self columnDescriptors:(columnHolder value)
    ].

    chgObj == columnAdaptor ifTrue:[
        col := columnAdaptor value.
        columnDescriptors do:[:aCol| aCol columnAdaptor:col].
      ^ self invalidate
    ].

    chgObj == editValue ifTrue:[
        newValue := editValue value.
        col := self selectedColumn.
        idx := self firstIndexSelected.

        col at:idx put:newValue.
        realValue := col at:idx.

        "/ !!!! editValue could be changed before set !!!! therefore: chgObj == editValue

        (chgObj == editValue and:[realValue ~= newValue]) ifTrue:[
            "/ some validation by the row-object; the stored value
            "/ is different from what I think.
            "/ update my input fields modelValue
            editValue value:realValue.
        ].

        modifiedChannel notNil ifTrue:[
            modifiedChannel value:true.
        ].
        ^ self
    ].

    chgObj == model ifTrue:[
        (what == #selectionIndex or:[what == #selection]) ifTrue:[
            ^ self selectRowIndex:(model selectionIndex copy)
        ].
        what == #list ifTrue:[
            ^ self listHolder:model list
        ].
        what == #value ifTrue:[
            model == listHolder ifTrue:[
                self list:(listHolder value)
            ] ifFalse:[
                self selectRowIndex:model value
            ]
        ].
        ^ self
    ].

    chgObj == listHolder ifTrue:[
        listHoldersList := listHolder value.

        what == #value ifTrue:[
            ^ self list:listHoldersList
        ].
        aPara isCollection ifTrue:[
            arg1 := aPara at:1.
            arg2 := aPara at:2.
        ] ifFalse:[
            arg1 := arg2 := aPara
        ].

        what == #at: ifTrue:[
            ^ self at:arg1 put:(listHoldersList at:arg1)
        ].

        what == #insert: ifTrue:[
            ^ self add:(listHoldersList at:arg1) beforeIndex:arg1
        ].
        
        what == #remove: ifTrue:[
            ^ self removeFrom:arg1 to:arg1
        ].
        what == #removeFrom: ifTrue:[
            listHoldersList size == 0 ifTrue:[self list:nil]
                          ifFalse:[self removeFrom:arg1 to:arg2].
            ^ self
        ].

        what == #insertCollection: ifTrue:[
            arg2 ~~ 0 ifTrue:[
                self addAll:(listHoldersList copyFrom:arg1 to:(arg1 + arg2 - 1)) beforeIndex:arg1
            ].
            ^ self.
        ].
        what == #replace: ifTrue:[
            self replaceFrom:arg1 to:arg2 with:listHoldersList startingAt:arg1.
          ^ self
        ].
        ^ self
    ].
    arg1 := aPara ? what.
    row := (what isNumber) ifTrue:[what] ifFalse:[chgObj].
    self invalidateVisibleRow:row readSelector:arg1.

    "Modified: / 30.1.2000 / 12:16:49 / cg"
! !

!DSVColumnView methodsFor:'drag & drop'!

canDrag
    "returns true if dragging is enabled
    "
    ^ dropSource notNil

!

dropSource
    "returns the dropSource or nil
    "
    ^ dropSource


!

dropSource:aDropSourceOrNil
    "set the dropSource or nil
    "
    dropSource := aDropSourceOrNil.


!

dropTarget
    "returns the dropTarget or nil
    "
    ^ dropTarget

!

dropTarget:aDropTragetOrNil
    "set the dropTarget or nil
    "
    dropTarget := aDropTragetOrNil.

!

startDragAt:aPoint

    dropSource notNil ifTrue:[
        dropSource startDragIn:self at:aPoint.
        buttonReleaseAction := buttonMotionAction := nil.
    ]
! !

!DSVColumnView methodsFor:'drawing'!

colorOnDevice:aColor
    "returns color on device
    "
    |col|

    aColor = Color noColor ifFalse:[
        col := colorMap at:aColor ifAbsent:nil.

        col isNil ifTrue:[
            colorMap at:aColor put:(col := aColor onDevice:device)
        ].
        ^ col
    ].
    ^ bgColor
!

forceRedraw
    "a redraw forced by any other component
    "
    shown ifTrue:[
        self invalidate
    ]
!

invalidate
    "recompute extent before repair range
    "
    self  recomputeHeightOfContents.
    super invalidate.


!

invalidateRowAt:aRowNr
    "redraw total row at an index
    "
    self invalidateRowAt:aRowNr colAt:0
!

invalidateRowAt:aRowNr colAt:aColNr
    "redraw either a single column in a row,
     or the complete visible row (in case of aColNr == 0).
     If the row/column is hidden, no redraw is done
    "
    |x "{ Class:SmallInteger }"
     y "{ Class:SmallInteger }"
     h "{ Class:SmallInteger }"
     w "{ Class:SmallInteger }"
     col|

    (shown and:[aRowNr notNil and:[aRowNr between:1 and:list size]]) ifTrue:[
        h := rowHeight.
        y := self yVisibleOfRowNr:aRowNr.

        y < margin ifTrue:[
            (h := h + y) <= margin ifTrue:[
                ^ self
            ].                                                  "/ row not visible
            h := h - margin.
            y := margin.
        ] ifFalse:[
            y >= height ifTrue:[^ self].
        ].
        aColNr ~~ 0 ifTrue:[                                    "/ redraw column in row
            col := (self columnAt:aColNr).
            w := col isNil ifTrue:0 ifFalse:[col width].
            x := self xVisibleOfColNr:aColNr.

            x < margin ifTrue:[
                (w := w + x) <= margin ifTrue:[
                    ^ self
                ].                                              "/ column not visible
                w := w - margin.
                x := margin.
            ] ifFalse:[
                x >= width ifTrue:[^ self].
            ]
        ] ifFalse:[                                             "/ redraw whole row
            x := margin.
            w := width.
        ].
        self invalidateX:x y:y width:w height:h
    ]
!

invalidateRows:aCollection
    "redraw some visible rows"

    shown ifTrue:[
        aCollection do:[:rowIndex |
            self invalidateRowAt:rowIndex
        ]
    ]
!

invalidateRowsFrom:aStart to:aStop
    "redraw visible row from start to stop
    "
    |size start stop y0 y1|

    shown ifTrue:[
        size  := list size.
        start := aStart notNil ifTrue:[aStart max:1]    ifFalse:[1].
        stop  := aStop  notNil ifTrue:[aStop  min:size] ifFalse:[size].

        start <= stop ifTrue:[
            y0 := (self yVisibleOfRowNr:start)              max:margin.
            y1 := ((self yVisibleOfRowNr:stop) + rowHeight) min:height.

            y0 < y1 ifTrue:[
                self invalidateX:margin y:y0 width:width height:(y1 - y0)
            ]
        ]
    ]
!

invalidateVisibleRow:aRow
    "redraw row if visible
    "
    self invalidateVisibleRow:aRow colAt:0
!

invalidateVisibleRow:aRow colAt:aColNr
    "redraw either a single column in a row,
     or the complete visible row (in case of aColNr == 0).
     If the row/column is hidden, no redraw is done
    "
    |start "{ Class:SmallInteger }"
     stop  "{ Class:SmallInteger }"
    |

    (start := self indexOfFirstRowShown) ~~ 0 ifTrue:[
        stop := (start + (height // rowHeight)) min:(list size).

        aRow isNumber ifTrue:[
            (aRow between:start and:stop) ifTrue:[
                self invalidateRowAt:aRow colAt:aColNr
            ]
        ] ifFalse:[
            start to:stop do:[:i|
                (self at:i) == aRow ifTrue:[
                    ^ self invalidateRowAt:i colAt:aColNr
                ]
            ]
        ]
    ]
!

invalidateVisibleRow:aRow readSelector:aSelector
    "redraw a column identified by its read selector; if no column with
     the specified read selector is detected, the whole line is drawn.
    "
    |row idx|

    aSelector isNil ifTrue:[
        idx := 0
    ] ifFalse:[
        (row := aRow) isNumber ifTrue:[
            (row := self at:aRow) isNil ifTrue:[ ^ self ]
        ].
        idx := columnDescriptors findFirst:[:aCol||desc|
            aCol description readSelector == aSelector
        ]
    ].
    self invalidateVisibleRow:aRow colAt:idx
!

redrawX:x y:y width:w height:h
    "redraw part of myself immediately, given logical coordinates 
    "
    |c0 savClip
     start "{ Class:SmallInteger }"
     stop  "{ Class:SmallInteger }"
     x0    "{ Class:SmallInteger }"
     x1    "{ Class:SmallInteger }"
     maxX  "{ Class:SmallInteger }"
     yTop  "{ Class:SmallInteger }"
     yBot  "{ Class:SmallInteger }"
     clHg  "{ Class:SmallInteger }"
     size  "{ Class:SmallInteger }"
    |

    shown ifFalse:[^ self].
    self paint:bgColor.

    columnDescriptors isEmpty ifTrue:[
        ^ self fillRectangleX:x y:y width:w height:h
    ].
    size  := list size.
    yTop  := margin - viewOrigin y.                   
    c0    := y - yTop max:0.
    start := (c0 // rowHeight) + 1.
    stop  := (c0 + h - 1 // rowHeight + 1) min:size.

    stop < start ifTrue:[
        ^ self fillRectangleX:x y:y width:w height:h
    ].
    savClip := clipRect.

    maxX := (x + w) min:(width - margin).
    x0   := margin - viewOrigin x.
    yTop := yTop + ((start - 1) * rowHeight).
    clHg := (stop - start + 1) * rowHeight.
    yBot := yTop + clHg.

    columnDescriptors do:[:aCol| |cw|
        x1 := x0 + aCol width.

        (x1 > x and:[x0 < maxX]) ifTrue:[
            |left right rect|
            left  := x0 max:x.
            right := x1 min:maxX.
            rect  := Rectangle left:left top:y width:(right - left) height:h.
        
            clipRect := nil.
            self clippingRectangle:rect.
            aCol redrawX:x0 y:yTop h:clHg  from:start to:stop.
        ].
        x0 := x1
    ].

    "/ restore old clipping rectangle
    self clippingRectangle:savClip.

    stop == size ifTrue:[
        yTop := y + h.
        yBot < (yTop - margin) ifTrue:[
            "/ clear to bottom of screen
            self paint:bgColor.
            self fillRectangleX:x y:yBot width:w height:(yTop - yBot).
        ]
    ].

    (c0 := w + x- x1) > 0 ifTrue:[
        "/ clear to right of screen
        self paint:bgColor.
        self fillRectangleX:x1 y:y width:c0 height:h.
    ].

    "/ draw the selection in the right part, after the last column
    x1 < (width-margin) ifTrue:[
        self hasRowSelection ifTrue:[
            self selectionIndicesDo:[:selIdx |
                |y|

                y := self yVisibleOfRowNr:selIdx.
                self paint:selectionBackgroundColor.
                self 
                    fillRectangleX:x1 y:y 
                    width:(width-margin-x1) height:rowHeight.
            ].
        ].
    ].
! !

!DSVColumnView methodsFor:'drawing interactors'!

drawCheckToggleAtX:xTop y:yTop w:rowWidth state:aState
    "draw a check toggle button
    "
    |e form
     y "{ Class:SmallInteger }"
     x "{ Class:SmallInteger }"
     h "{ Class:SmallInteger }"
     w "{ Class:SmallInteger }"
    |
    w := checkToggleExtent x.
    h := checkToggleExtent y.
    y := yTop + (rowHeight - h // 2).
    x := xTop + (rowWidth  - w // 2).
    h odd ifTrue:[y := y + 1].

    (form := checkToggleActiveImage) isNil ifTrue:[
        self paint:bgColor.
        self fillRectangleX:x y:y width:w height:h.
        self drawEdgesAtX:x   y:y width:w height:h level:checkToggleLevel on:self.

        aState ifFalse:[
            ^ self
        ].
        self paint:fgColor on:bgColor.
        form := checkToggleForm
    ] ifFalse:[
        aState ifFalse:[form := checkTogglePassiveImage]
    ].
    e := (checkToggleExtent - form extent) // 2.
    self displayForm:form x:(x + e x) y:(y + e y).





!

drawComboButtonAtX:xTop y:yTop w:rowWidth
    "draw a combo button
    "
    |e
     x "{ Class:SmallInteger }"
     y "{ Class:SmallInteger }"
     h "{ Class:SmallInteger }"
     w "{ Class:SmallInteger }"
    |
    w := comboButtonExtent x.
    h := comboButtonExtent y.
    y := yTop + (rowHeight - h // 2).
    x := xTop + (rowWidth  - w - separatorSize - 1).
    e := (comboButtonExtent - comboButtonForm extent) // 2.

    self paint:bgColor.
    self fillRectangleX:x y:y width:w height:h.
    self drawEdgesAtX:x   y:y width:w height:h level:comboButtonLevel on:self.
    self paint:fgColor on:bgColor.
    self displayForm:comboButtonForm x:(x + e x) y:(y + e y)

!

drawEdgesAtX:x y:y width:w height:h level:aLevel on:aGC
    "draw edges for a cell or label
    "
    aGC  drawEdgesForX:x
                     y:y 
                 width:w
                height:h
                 level:aLevel 
                shadow:buttonShadowColor 
                 light:buttonLightColor
            halfShadow:buttonHalfShadowColor 
             halfLight:buttonHalfLightColor
                 style:ButtonEdgeStyle.

! !

!DSVColumnView methodsFor:'enumerating columns'!

columnsDo:aOneArgBlock
    "evaluate the argument, aOneArgBlock for every column
    "
    columnDescriptors do:aOneArgBlock


!

columnsFrom:start to:stop do:aOneArgBlock
    "evaluate the argument, aOneArgBlock for the columns with index start to
     stop in the collection of column descriptors
    "
    columnDescriptors from:start to:stop do:aOneArgBlock


! !

!DSVColumnView methodsFor:'event handling'!

buttonControlPressAtRowNr:aStartRow
    |isSelected prvRow doAdd chgSet|

    buttonMotionAction := nil.
    isSelected         := self isInSelection:aStartRow.

    isSelected ifTrue:[ self removeRowFromSelection:aStartRow ]
              ifFalse:[ self addRowToSelection:aStartRow ].

    multipleSelectOk ifFalse:[ ^ self ].

    prvRow := aStartRow.
    chgSet := IdentitySet new.
    doAdd  := isSelected not.

    buttonMotionAction := [:p| |rowNr mustRestore step f|
        rowNr := self yVisibleToRowNr:(p y).

        (rowNr notNil and:[rowNr ~~ prvRow]) ifTrue:[
            rowNr == aStartRow ifTrue:[
                mustRestore := true
            ] ifFalse:[
                rowNr > aStartRow ifTrue:[ mustRestore := (rowNr < prvRow) ]
                                 ifFalse:[ mustRestore := (rowNr > prvRow) ].
            ].
            prvRow > rowNr ifTrue:[ step := -1 ]
                          ifFalse:[ step :=  1 ].
            mustRestore ifTrue:[
                [ prvRow ~~ rowNr ] whileTrue:[
                    (chgSet removeIdentical:prvRow ifAbsent:nil) notNil ifTrue:[
                        doAdd ifFalse:[ self addRowToSelection:prvRow ]
                               ifTrue:[ self removeRowFromSelection:prvRow ].
                    ].
                    prvRow := prvRow + step.
                ].
            ] ifFalse:[
                [ prvRow ~~ rowNr ] whileTrue:[
                    prvRow := prvRow + step.

                    doAdd ~~ (self isInSelection:rowNr) ifTrue:[
                        chgSet add:prvRow.

                        doAdd ifTrue:[ self addRowToSelection:prvRow ]
                             ifFalse:[ self removeRowFromSelection:prvRow ].
                    ]
                ].
            ].
        ].
    ].
!

buttonMotion:buttonMask x:x y:y
    "mouse-move while button was pressed - handle multiple selection changes
    "
    self isEnabled ifFalse:[^ self].

    buttonMotionAction notNil ifTrue:[
        buttonMotionAction value:(x@y).

        buttonMotionAction notNil ifTrue:[
            autoScroll ifTrue:[
                "/ if moved outside of view, start autoscroll
                (y < 0) ifTrue:[
                    ^ self startAutoScroll:[self scrollUp] distance:y.
                ].
                (y > height) ifTrue:[
                    ^ self startAutoScroll:[self scrollDown] distance:(y - height).
                ].
            ]
        ]
    ].
    self stopAutoScroll.
!

buttonMultiPress:button x:x y:y
    "a button was pressed twice - handle doubleclick here
    "
    buttonMotionAction := buttonReleaseAction := nil.

    self isEnabled ifFalse:[^ self].

    ((button == 1) or:[button == #select]) ifFalse:[
        ^ super buttonMultiPress:button x:x y:y
    ].
    self numberOfSelections == 1 ifTrue:[
        self firstIndexSelected == (self yVisibleToRowNr:y) ifTrue:[
            (     selectedColIndex == 0
              or:[selectedColIndex == (self xVisibleToColNr:x)]
            ) ifTrue:[
                self doubleClicked
            ]
        ]
    ]
!

buttonPress:button x:x y:y
    "a button was pressed - handle selection here
    "
    |rowNr colNr menu sensor clickPoint|

    buttonMotionAction := buttonReleaseAction := nil.

    self isEnabled ifFalse:[^ self].

    sensor := self sensor.

    ((button == 2) or:[button == #menu]) ifTrue:[
        menu := self findMenuForSelection.

        menu notNil ifTrue:[ self startUpMenu:menu ]
                   ifFalse:[ super buttonPress:button x:x y:y ].
        ^ self
    ].

    (     (rowNr := self yVisibleToRowNr:y) notNil
     and:[(colNr := self xVisibleToColNr:x) notNil]
    ) ifFalse:[
        super buttonPress:button x:x y:y.
        ^ self
    ].

    sensor ctrlDown ifTrue:[
        self buttonControlPressAtRowNr:rowNr.
        ^ self
    ].

    sensor shiftDown ifTrue:[
        (multipleSelectOk and:[selectedRowIndex size ~~ 0]) ifTrue:[
            self selectRowFrom:(selectedRowIndex min min:rowNr)
                            to:(selectedRowIndex max max:rowNr).
        ] ifFalse:[
            self selectColIndex:0 rowIndex:rowNr.
        ].
        ^ self
    ].

    (self canDrag and:[self isSelected:rowNr inColumn:colNr]) ifTrue:[
        clickPoint := x @ y.
        buttonReleaseAction := [ self selectRowAt:rowNr colAt:colNr atPoint:(x @ y) ].

        buttonMotionAction := [:aPoint|
            (clickPoint dist:aPoint) > 5.0 ifTrue:[
                buttonReleaseAction := buttonMotionAction := nil.
                self startDragAt:aPoint.
            ]
        ].
        ^ self.
    ].

    self withWaitCursorDo:[
        self selectRowAt:rowNr colAt:colNr atPoint:(x @ y)
    ].

    (multipleSelectOk and:[selectedColIndex == 0 and:[selectedRowIndex size == 1]]) ifTrue:[
        buttonMotionAction := [:p| |r|
            r := self yVisibleToRowNr:(p y).
            r notNil ifTrue:[ self selectRowFrom:rowNr to:r ].
        ]
    ].
!

buttonRelease:button x:x y:y

    buttonMotionAction := nil.
    self stopAutoScroll.

    buttonReleaseAction notNil ifTrue:[
        buttonReleaseAction value.
        buttonReleaseAction := nil.
    ].
    super buttonRelease:button x:x y:y
!

characterPress:aChar x:x y:y
    "search row in column at x/y starting its printable label with cahracter.
    "
    |colNr rowNr lsize found column|

    lsize  := list size.

"/    (rowIfAbsentBlock notNil 
"/     or:[x isNil
"/     or:[(colNr := self xVisibleToColNr:x) isNil]]) ifTrue:[
"/        ^ self
"/    ].
    colNr := self findFirstColumnWithStringFrom:1 to:lsize.
    colNr isNil ifTrue:[ ^ self ].

    rowNr  := self lastIndexSelected.
    column := self columnAt:colNr.
    found  := 0.

    lsize > rowNr ifTrue:[
        "/ search to end
        found := column findRowNrStartingWithChar:aChar start:(rowNr + 1) stop:lsize.
    ].

    (found == 0 and:[rowNr > 1]) ifTrue:[
     "/ search from begin
        found := column findRowNrStartingWithChar:aChar start:1 stop:(rowNr - 1)
    ].
    found ~~ 0 ifTrue:[
        self selectColIndex:colNr rowIndex:found.
    ].

    "Modified: / 21.5.1998 / 03:30:22 / cg"
!

contentsChanged
    "contents changed - move origin up if possible
    "
    |y|

    shown ifTrue:[
        self recomputeHeightOfContents.
        y := self maxViewOriginY.

        viewOrigin y > y ifTrue:[
            scrollWhenUpdating ~~ false ifTrue:[
                self scrollTo:(viewOrigin x @ y)
            ]
        ] ifFalse:[
            self updateEditViewOrigin.
        ]
    ].
    super contentsChanged
!

doubleClicked
    "handle a double click
    "
    |col sel idx|

    self hasSelection ifTrue:[
        idx := self firstIndexSelected.
        col := self selectedColumn.
        (col notNil and:[(sel := col doubleClickedSelector) notNil]) ifTrue:[
            col doubleClickOn:idx
        ] ifFalse:[
            doubleClickActionBlock notNil ifTrue:[
                doubleClickActionBlock value:idx
            ]
        ]
    ]
!

findFirstColumnWithStringFrom:start to:stop
    start to:stop do:[:eachNr| 
        |row lbl|
        row := self at:eachNr.
        columnDescriptors keysAndValuesDo:[:colNr :col |    
            lbl := col shownValueForRow:row rowNr:eachNr.

            (lbl isSequenceable and:[lbl isString not]) ifTrue:[
                lbl := lbl at:1 ifAbsent:nil
            ].

            (lbl respondsTo:#string) ifTrue:[
                lbl := lbl string.
            ].
            (lbl size > 0 and:[(lbl at:1) isCharacter]) ifTrue:[
                ^ colNr
            ]
        ]
    ].
    ^ nil
!

findMenuForSelection
    "find the middle button menu for the current selection; returns the menu or nil
    "
    |col row menu|

    self numberOfSelections == 1 ifTrue:[
        row := self at:(self firstIndexSelected).
        col := self selectedColumn.

        (col notNil and:[(menu := col menuForRow:row orAdaptor:columnAdaptor) notNil]) ifTrue:[
            ^ menu
        ].
        col := columnDescriptors detect:[:c| c rendererType == #rowSelector]
                                 ifNone:[nil].

        col notNil ifTrue:[
            ^ col menuForRow:row inApplication:(self application)
        ]
    ].
    ^ nil
!

keyPress:aKey x:x y:y
    "a key was pressed - handle page-keys here
    "
    <resource: #keyboard (#PreviousPage #NextPage #HalfPageUp #HalfPageDown
                          #BeginOfText #EndOfText #ScrollUp #ScrollDown
                          #CursorUp #CursorDown #CursorRight #CursorLeft #SelectAll)>

    |maxColNr firstSelRowNr minSelRowNr maxSelRowNr selRowNr selColNr 
     key column isTab listSize hasSelectables rawKey|

    self isEnabled ifFalse:[^ self].

    (listSize := list size) == 0 ifTrue:[
        ^ super keyPress:aKey x:x y:y
    ].
    aKey isCharacter ifTrue:[
        ^ self characterPress:aKey x:x y:y
    ].

    (aKey == #SelectAll) ifTrue:[
        multipleSelectOk ifTrue:[
            self selectAllRows. 
        ].
        ^ self
    ].

    aKey == #PreviousPage ifTrue:[^ self pageUp].
    aKey == #NextPage     ifTrue:[^ self pageDown].
    aKey == #HalfPageUp   ifTrue:[^ self halfPageUp].
    aKey == #HalfPageDown ifTrue:[^ self halfPageDown].
    aKey == #BeginOfText  ifTrue:[^ self scrollToTop].
    aKey == #EndOfText    ifTrue:[^ self scrollToBottom].
    aKey == #ScrollUp     ifTrue:[^ self scrollUp]. 
    aKey == #ScrollDown   ifTrue:[^ self scrollDown]. 

    aKey == #Return ifTrue:[
        self numberOfSelections == 1 ifTrue:[self doubleClicked].
        ^ self
    ].

    hasSelectables := false.

    columnDescriptors do:[:aCol|
        (hasSelectables or:[aCol rendererType == #rowSelector]) ifFalse:[
            hasSelectables := aCol description canSelect
        ]
    ].

    rawKey := (WindowGroup lastEventQuerySignal query) rawKey.

    (aKey == #CursorUp 
        or:[aKey == #CursorDown
        or:[rawKey == #Home
        or:[rawKey == #End] 
    ]]) ifTrue:[
        (hasSelectables or:[selectRowOnDefault]) ifTrue:[
            minSelRowNr := self minIndexSelected.
            maxSelRowNr := self maxIndexSelected.

            selColNr := hasSelectables ifFalse:[0]
                                        ifTrue:[selectedColIndex].
        
            aKey == #CursorUp ifTrue:[
                selRowNr := minSelRowNr > 1 ifTrue:[minSelRowNr - 1] ifFalse:[listSize].
            ] ifFalse:[
                aKey == #CursorDown ifTrue:[
                    selRowNr := maxSelRowNr < listSize ifTrue:[maxSelRowNr + 1] ifFalse:[1].
                ] ifFalse:[
                    aKey == #BeginOfLine ifTrue:[
                        selRowNr := 1.
                    ] ifFalse:[
                        selRowNr := listSize.
                    ]
                ]
            ].

            ((aKey == #CursorUp) or:[aKey == #CursorDown]) ifTrue:[
                (multipleSelectOk and:[self sensor shiftDown]) ifTrue:[
                    self addRowToSelection:selRowNr.

                    aKey == #CursorDown ifTrue:[
                        self makeLineVisible:((selRowNr+1) min:listSize)
                    ] ifFalse:[
                        self makeLineVisible:((selRowNr-1) max:1)
                    ].
                    ^ self
                ]
            ].
            self selectColIndex:selColNr rowIndex:selRowNr
        ].
        ^ self
    ].

    hasSelectables ifFalse:[
        ^ super keyPress:aKey x:x y:y
    ].

    "/ CURSOR LEFT/RIGHT or TABING
    (aKey == #CursorLeft or:[aKey == #CursorRight]) ifTrue:[
        isTab := false.
        key   := aKey.
    ] ifFalse:[
        tabIntern ifFalse:[
            ^ super keyPress:aKey x:x y:y
        ].

        (aKey includesString:'Tab') ifTrue:[
            key := (self sensor shiftDown or:[aKey includesString:'Left']) 
                        ifTrue:[#CursorLeft]
                        ifFalse:[#CursorRight]
        ] ifFalse:[
            (aKey == #FocusPrevious or:[aKey == #FocusNext]) ifFalse:[
                ^ super keyPress:aKey x:x y:y
            ].
            key := aKey == #FocusPrevious ifTrue:[#CursorLeft] ifFalse:[#CursorRight].
        ].
        isTab := true.
    ].

    maxColNr := self numberOfColumns.
    selColNr := selectedColIndex.

    firstSelRowNr := self firstIndexSelected.

    key == #CursorLeft ifTrue:[
        selRowNr := firstSelRowNr.
        selRowNr == 0 ifTrue:[selRowNr := listSize].
        selColNr == 0 ifTrue:[selColNr := maxColNr + 1].

        [true] whileTrue:[
            (selColNr := selColNr - 1) == 0 ifTrue:[
                (selRowNr := selRowNr - 1) == 0 ifTrue:[
                    isTab ifTrue:[
                        tabAtStartAction notNil ifTrue:[
                            tabAtStartAction value
                        ] ifFalse:[
                            self deselect.
                            self windowGroup focusPreviousFrom:self
                        ]
                    ].
                    ^ self
                ].
                selColNr := maxColNr
            ].
            column := self columnAt:selColNr.

            (column rendererType ~~ #rowSelector and:[column canSelect:selRowNr]) ifTrue:[
                ^ self selectColIndex:selColNr rowIndex:selRowNr.
            ]
        ]
    ].

    selRowNr := firstSelRowNr.
    selRowNr == 0 ifTrue:[selRowNr := 1].

    [true] whileTrue:[
        (selColNr := selColNr + 1) > maxColNr ifTrue:[
            (selRowNr := selRowNr + 1) > listSize ifTrue:[
                isTab ifTrue:[
                    tabAtEndAction notNil ifTrue:[
                        tabAtEndAction value
                    ] ifFalse:[
                        self deselect.
                        self windowGroup focusNextFrom:self
                    ]
                ].
                ^ self
            ].
            selColNr := 1
        ].
        column := self columnAt:selColNr.

        (column rendererType ~~ #rowSelector and:[column canSelect:selRowNr]) ifTrue:[
            ^ self selectColIndex:selColNr rowIndex:selRowNr
        ]
    ].
!

originChanged:delta
    "this one is sent, after the origin of my contents has changed -
     tell dependents (i.e. scrollers) about this
    "
    super originChanged:delta.
    self updateEditViewOrigin.
!

sizeChanged:how
    "size changed - move origin up if possible
     change the layout of the labelView dependent on my layout
    "
    |selectionWasVisible|

    selectionWasVisible := self isSelectionVisibleIn:(previousExtent ? self extent).

    previousExtent := self extent.
    super sizeChanged:how.

    "/ no - must compute even if not visible.
    "/ (could be invisible in a notebook ...)
    realized ifTrue:[                   "used to be: shown" 
"/        labelView isVisible ifTrue:[
"/            lyt := labelView layout.
"/            lyt  leftOffset:(layout leftOffset).
"/            lyt rightOffset:(layout rightOffset).
"/            labelView layout:lyt.
"/        ].
        self fitColumns.
        selectionWasVisible ifTrue:[
            self makeSelectionVisible.
        ]
    ].
! !

!DSVColumnView methodsFor:'focus handling'!

canTab
    super canTab ifTrue:[
        ^ editView isNil
    ].
    ^ false
!

wantsFocusWithPointerEnter
    "views which like to take the keyboard focus
     when the pointer enters can do so by redefining this
     to return true"

    |pref|

    editView ifNotNil:[^ false].

    pref := UserPreferences current focusFollowsMouse.
    (pref ~~ false
    and:[(styleSheet at:#'selection.requestFocusOnPointerEnter' default:true)
    ]) ifTrue:[
        list size > 0 ifTrue:[
            ^ true
        ]
    ].
    ^ false
! !

!DSVColumnView methodsFor:'gc operations'!

imageOnMyDevice:anImage
    "associate image to device and clear pixel mask; returns the new image.
    "
    |deviceImage|

    anImage isNil ifTrue:[^ anImage].

    deviceImage := anImage onDevice:device.
    deviceImage isImage ifTrue:[
        deviceImage clearMaskedPixels.
    ].
    ^ deviceImage
"/    |image|
"/
"/    (image := anImage) notNil ifTrue:[
"/        image device ~~ device ifTrue:[
"/            image := image copy onDevice:device.
"/        ].
"/        image isImage ifTrue:[
"/            image := image clearMaskedPixels
"/        ]
"/    ].
"/    ^ image
!

registerImage:anImage key:aKey
    "any row can register an image with a unique identifier a key symbol
    "
    |img|

    (img := registererImages at:aKey ifAbsent:nil) notNil ifTrue:[
        ^ img
    ].
    img := self imageOnMyDevice:anImage.
    registererImages at:aKey put:img.
    ^ img
!

registeredImageAt:aKey
    "any row can register an image with a unique identifier
    "
    ^ registererImages at:aKey ifAbsent:nil
!

releaseAllRegisteredImages
    "release all registered images
    "
    registererImages := IdentityDictionary new.
! !

!DSVColumnView methodsFor:'initialization & release'!

create
    "set color on device
    "
    super create.

    fgColor     := fgColor     onDevice:device.
    bgColor     := bgColor     onDevice:device.
    selectionForegroundColor := selectionForegroundColor onDevice:device.
    selectionBackgroundColor := selectionBackgroundColor onDevice:device.

    buttonShadowColor := buttonShadowColor onDevice:device.
    buttonLightColor  := buttonLightColor onDevice:device.

    buttonHalfShadowColor notNil ifTrue:[
        buttonHalfShadowColor := buttonHalfShadowColor onDevice:device
    ].

    buttonHalfLightColor notNil ifTrue:[
        buttonHalfLightColor := buttonHalfLightColor onDevice:device
    ].
    rowSelectorForm         := self imageOnMyDevice:rowSelectorForm.
    checkToggleActiveImage  := self imageOnMyDevice:checkToggleActiveImage.
    checkTogglePassiveImage := self imageOnMyDevice:checkTogglePassiveImage.
    comboButtonForm         := self imageOnMyDevice:comboButtonForm.
    checkToggleForm         := self imageOnMyDevice:checkToggleForm.
!

initStyle
    "setup colors
    "
    |button widget|

    super initStyle.

    DefaultForegroundColor isNil ifTrue:[
        self class updateStyleCache
    ].
    fgColor     := DefaultForegroundColor.
    bgColor     := DefaultBackgroundColor.
    selectionForegroundColor := DefaultHilightForegroundColor.
    selectionBackgroundColor := DefaultHilightBackgroundColor.

    shadowColor isNil ifTrue:[
        shadowColor := Color grayPercent:40.
    ].

    lightColor isNil ifTrue:[
        lightColor := Color grayPercent:75
    ].

    buttonLightColor    := ButtonLightColor  ? lightColor.
    buttonShadowColor   := ButtonShadowColor ? shadowColor.

    (ButtonEdgeStyle == #soft) ifTrue:[
        buttonHalfShadowColor := ButtonHalfShadowColor.
        buttonHalfLightColor  := ButtonHalfLightColor.

        buttonHalfShadowColor isNil ifTrue:[
            buttonHalfShadowColor := buttonShadowColor lightened
        ]
    ].

    buttonHalfShadowColor isNil ifTrue:[
        buttonHalfShadowColor := Color gray
    ].

    rowSelectorForm         := self class rowSelector.
    checkToggleActiveImage  := CheckToggleActiveImage.
    checkTogglePassiveImage := CheckTogglePassiveImage.

    ComboButtonForm isNil ifTrue:[
        widget            := ComboListView new.
        button            := widget menuButton.
        ComboButtonForm   := button label.
        ComboButtonLevel  := button offLevel.
        ComboButtonExtent := (button preferredExtent x) @ (widget preferredExtent y).
    ].

    CheckToggleForm isNil ifTrue:[
        widget            := CheckToggle new.
        CheckToggleForm   := widget label.
        CheckToggleLevel  := widget offLevel.
        CheckToggleExtent := widget preferredExtent.
    ].
    comboButtonForm   := ComboButtonForm.
    comboButtonLevel  := ComboButtonLevel.
    comboButtonExtent := ComboButtonExtent.

    checkToggleForm   := CheckToggleForm.
    checkToggleLevel  := CheckToggleLevel.
    checkToggleExtent := CheckToggleExtent.
!

initialize
    "set default attributes
    "
    super initialize.
    self lineWidth:0.

    tabIntern          := true.
    useIndex           := true.
    viewOrigin         := 0@0.
    font               := font onDevice:device.
    rowHeight          := font height.
    multipleSelectOk   := false.                        "/ multiselect disabled
    selectedRowIndex   := selectedColIndex  := 0.       "/ no selection
    registererImages   := Dictionary new.
    columnDescriptors  := #().
    beDependentOfRows  := false.
    verticalSpacing    := self class verticalSpacing.
    horizontalSpacing  := self class horizontalSpacing.
    colorMap           := Dictionary new.
    catchChangeEvents  := false.
    rowFontAscent      := 1.                            "/ dummy initialization
    separatorSize      := 1.                            "/ separators mode 2D
    selectRowOnDefault := true.
    autoScroll         := true.
!

mapped
    "set selection if exists after mapping
    "
"/    |idx|
"/
    super mapped.
"/    needFitColumns == true ifTrue:[
"/        self fitColumns
"/    ].
"/
"/    (idx := self firstIndexSelected) ~~ 0 ifTrue:[
"/        self scrollToRowAt:idx colAt:0.        
"/    ].
!

realize
    "recompute contents and fit columns to view
    "
    |selection|

    self  bitGravity:#NorthWest.
    self  recomputeHeightOfContents.

    selection := 0.

    model notNil ifTrue:[
        (model respondsTo:#selectionIndex) ifTrue:[
            selection := model selectionIndex
        ] ifFalse:[
            model == listHolder ifFalse:[
                selection := model value
            ]
        ]
    ].
    self selectRowIndex:selection.

    super realize.
    self  fitColumns.
!

realized
    "set selection if exists after mapping
    "
    |idx|

    super realized.

    needFitColumns == true ifTrue:[
        self fitColumns
    ].

    (idx := self firstIndexSelected) ~~ 0 ifTrue:[
        self scrollToRowAt:idx colAt:0.        
    ].
!

release
    "remove dependencies
    "
    self columnHolder:nil.

    listHolder    removeDependent:self.

    columnAdaptor isValueModel ifTrue:[
        columnAdaptor removeDependent:self
    ].

    self beDependentOfRows:false.
    super release
! !

!DSVColumnView methodsFor:'obsolete'!

has3Dsepartors
    "shouldn't be used any more
    "
    ^ self has3Dseparators
!

has3Dsepartors:aBool
    "shouldn't be used any more
    "
    self has3Dseparators:aBool

! !

!DSVColumnView methodsFor:'private'!

anyColumnHasBackground
    columnDescriptors isNil ifTrue:[^ false].
    columnDescriptors do:[:eachCol |
        |bg|

        bg := eachCol backgroundColor.
        (bg notNil and:[bg isImageOrForm]) ifTrue:[^ true].
        eachCol bgSelector notNil ifTrue:[^ true].
    ].
    ^ false.
!

anyColumnHasPotentialNonConstantBackground
    columnDescriptors isNil ifTrue:[^ false].
    columnDescriptors do:[:eachCol | eachCol hasPotentialNonConstantBackground ifTrue:[^ true]].
    ^ false.
!

destroyEditView
    "destroy the edit view; release KeyboardForwarder
    "
    editView notNil ifTrue:[
        editView withAllSubViewsDo:[:aView|
            aView delegate:nil
        ].
        editView destroy.
        editView := nil.
        self windowGroup focusView:nil.
    ].


!

detectViewAt:aPoint in:aView
    "returns the view at a point
    "
    aView isNil ifTrue:[^ nil].
    ^ aView detectViewAt:aPoint.

"/ cg: old code was (refactored to use common code)
"/
"/    |p|
"/
"/    (aView notNil and:[aView subViews notNil]) ifTrue:[
"/        aView subViews do:[:sv|
"/            p := device translatePoint:aPoint fromView:self toView:sv.
"/
"/            (p x >= 0 and:[p y >= 0 and:[p x <= sv width and:[p y <= sv height]]]) ifTrue:[
"/                ^ self detectViewAt:aPoint in:sv
"/            ]
"/        ]
"/    ].
"/    ^ aView

    "Modified: / 10.10.2001 / 13:53:24 / cg"
!

fitColumns
    "fit columns to view; 
    "
    |selectedColumn list width changed dX sz oldOrgX newOrgX|

    preferredExtent isNil ifTrue:[
        self preferredExtent.
    ].

    columnDescriptors isEmpty ifTrue:[
        ^ self
    ].
    width := 0.
    list  := OrderedCollection new.

    columnDescriptors do:[:aCol|
        aCol canResize ifTrue:[
            changed := true.
            aCol invalidate.
            aCol hasRelativeWidth ifFalse:[list add:aCol].
        ].
        width := width + aCol minWidth.
    ].
    preferredExtent x:width.
    width := self innerWidth - width.

    (list notEmpty and:[width > 0]) ifTrue:[
        sz := list size.
        width >= sz ifTrue:[
            dX := width // sz.
            list do:[:aCol|aCol growWidth:dX].
            width := width - (dX * sz).
        ].
        width ~~ 0 ifTrue:[list last growWidth:width].
    ].

    shown ifTrue:[
        self      invalidate.
        labelView invalidate.

        (oldOrgX := viewOrigin x) ~~ 0 ifTrue:[
            "/ UPDATE VIEW-ORIGIN X

            width   := preferredExtent x.
            newOrgX := (width - self innerWidth) max:0.

            newOrgX < oldOrgX ifTrue:[
                self originWillChange.
                viewOrigin := newOrgX @ (viewOrigin y).
                self originChanged:((newOrgX - oldOrgX) @ 0).
            ]
        ].

        "/ cg: do not scroll to selection when a column-size is changed

"/        self hasSelection ifTrue:[
"/            editView notNil ifTrue:[
"/                editView width:(self selectedColumn width - (2 * separatorSize)).
"/            ].
"/            self scrollToSelection.
"/        ].

        "/ ca: but resize the editView

        editView notNil ifTrue:[
            selectedColumn := self selectedColumn.
            selectedColumn notNil ifTrue:[
                editView width:(selectedColumn width - (2 * separatorSize)).
            ]
        ].

        self contentsChanged.
    ].
!

maxViewOriginY
    "returns the maximum possible y of the view origin
    "
    |y|

    y := self heightOfContents - self innerHeight.
  ^ y max:0

!

updateEditViewOrigin
    "update origin of the editView
    "
    |x y|

    editView notNil ifTrue:[
        y := self yVisibleOfRowNr:(self firstIndexSelected).
        x := self xVisibleOfColNr:selectedColIndex.

        editView origin:(x @ y + separatorSize).
    ].

!

xVisibleOfColNr:aColNr
    "returns visible x assigned to a column number
    "
    |x
     end "{ Class:SmallInteger }"
    |
    x := margin - viewOrigin x.

    aColNr > 1 ifTrue:[
        end := aColNr - 1.

        columnDescriptors from:1 to:end do:[:aCol|
            x := x + aCol width
        ]
    ].
  ^ x

!

xVisibleToColNr:x
    "returns the column number assigned to a physical x or nil
    "
    |x0
     nr "{ Class:SmallInteger }"
    |

    x0 := x + viewOrigin x - margin.
    nr := 1.

    columnDescriptors do:[:aCol|
        x0 := x0 - aCol width.
        x0 <= 0 ifTrue:[^ nr].
        nr := nr + 1.
    ].
    ^ nil.

!

yVisibleOfRowNr:aRowNr
    "returns visible y assigned to the row number
    "
    ^ (aRowNr - 1) * rowHeight + margin - viewOrigin y

!

yVisibleToRowNr:y
    "returns the row number assigned to a physical y or nil
    "
    |rowNr|

    rowNr := (y + viewOrigin y - margin) // rowHeight + 1.

    (rowNr between:1 and:(list size)) ifTrue:[
        ^ rowNr
    ].
    ^ nil
! !

!DSVColumnView methodsFor:'queries'!

hasOpenEditor
    ^ editView notNil
!

indexOfFirstRowShown
    "returns index of first row shown
    "
    |idx|

    idx := (viewOrigin y // rowHeight) + 1.
  ^ (idx <= list size) ifTrue:[idx] ifFalse:[0]

!

isEnabled
    ^ enableChannel value ~~ false
!

numberOfColumns
    "returns number of columns
    "
    ^ columnDescriptors size

!

numberOfRows
    "returns number of raws
    "
    ^ list size

!

rowHeight
    "get the height of the highest row in pixels
    "
    ^ rowHeight

!

separatorSize
    "returns vertical/horizontal size of a separator dependent on the
     3D effect.
    "
    ^ separatorSize

!

size
    "returns number of raws
    "
    ^ list size

! !

!DSVColumnView methodsFor:'recomputation'!

hasPreferredExtent
    "returns true if preferred extent is accumulated
    "
    ^ preferredExtent notNil
!

preferredExtent
    "recompute preferred extent; raise notification
    "
    |x "{ Class:SmallInteger }"
     h "{ Class:SmallInteger }"
    |

    preferredExtent notNil ifTrue:[
        ^ preferredExtent
    ].
    x := 3.
    h := 0.

    columnDescriptors do:[:aCol|
        h := (aCol heightOfHighestRow) max:h.
        x := x + (aCol minWidth).
    ].
    h == 0 ifTrue:[h := font height].
    rowHeight       := (h + separatorSize + verticalSpacing + verticalSpacing + 1) // 2 * 2.
    preferredExtent := x @ (list size * rowHeight).
    rowFontAscent   := font ascent.

  ^ preferredExtent


!

preferredExtentChanged
    "called if the preffered extent changed
    "
    |x "{ Class:SmallInteger }"
     y "{ Class:SmallInteger }"
    |
    y := viewOrigin y.
    x := viewOrigin x.

    (y ~~ 0 or:[x ~~ 0]) ifTrue:[
        self originWillChange.

        viewOrigin := 0 @ 0.
        preferredExtent := nil.
        self originChanged:(x negated  @ y negated).
    ]
!

recomputeHeightOfContents
    "recompute height of contents( scrolling )
    "
    preferredExtent notNil ifTrue:[
        preferredExtent y:(rowHeight * list size)
    ] ifFalse:[
        self preferredExtent
    ].
! !

!DSVColumnView methodsFor:'scroller interface'!

heightOfContents
    "return the height of the contents in pixels
    "
    preferredExtent isNil ifTrue:[
        self preferredExtent
    ].
    ^ preferredExtent y
!

innerHeight
    "returns the inner height of the contents shown
    "
    ^ height - margin - margin

!

verticalScrollStep
    "return the amount to scroll when stepping up/down.
    "
    ^ rowHeight



!

viewOrigin
    "return the viewOrigin; thats the coordinate of the contents 
     which is shown topLeft in the view.
    "
    ^ viewOrigin

!

widthOfContents
    "return the width of the contents in pixels
    "
    preferredExtent isNil ifTrue:[
        self preferredExtent
    ].
    ^ preferredExtent x

!

xOriginOfContents
    "return the horizontal origin of the contents in pixels
    "
    ^ viewOrigin x 

!

yOriginOfContents
    "return the vertical origin of the contents in pixels
    "
    ^ viewOrigin y

! !

!DSVColumnView methodsFor:'scrolling'!

scrollTo:anOrigin redraw:doRedraw
    "change origin to have newOrigin be visible at the top-left.
    "
    |newOrg dltOrg wg
     h       "{ Class:SmallInteger }"
     w       "{ Class:SmallInteger }"
     x       "{ Class:SmallInteger }"
     x0      "{ Class:SmallInteger }"
     x1      "{ Class:SmallInteger }"
     y       "{ Class:SmallInteger }"
     y0      "{ Class:SmallInteger }"
     y1      "{ Class:SmallInteger }"
     dX      "{ Class:SmallInteger }"
     dY      "{ Class:SmallInteger }"
     innerHG "{ Class:SmallInteger }"
     innerWT "{ Class:SmallInteger }"
     lMargin
    |

    shown ifFalse:[
        ^ self
    ].

    (wg := self windowGroup) notNil ifTrue:[
        wg processRealExposeEventsFor:self.
    ].

    innerWT := self innerWidth.
    innerHG := self innerHeight.

    h := viewOrigin y.

    (y := anOrigin y) > h ifTrue:[              "/ end of contents
        y > (dY := self maxViewOriginY) ifTrue:[
            y := dY max:h
        ]
    ] ifFalse:[
        y := y max:0.
    ].

    (x := anOrigin x) > 0 ifTrue:[
        x := x min:(self widthOfContents - innerWT).
    ].
    x      := x max:0.
    newOrg := (x @ y).
    dltOrg := newOrg - viewOrigin.
    dX     := dltOrg x.
    dY     := dltOrg y.

    (dX == 0 and:[dY == 0]) ifTrue:[
        ^ self
    ].
    self originWillChange.
    viewOrigin := newOrg.

    doRedraw ifFalse:[
        ^ self originChanged:dltOrg
    ].

    dY ~~ 0 ifTrue:[                            "/ SCROLL VERTICAL
        dY := dY abs.

        (dX ~~ 0 or:[innerHG - dY < 20]) ifTrue:[
            self invalidate.
        ] ifFalse:[                             "/ COPY VERTICAL
            y0 := y1 := margin + dY.
            h  := innerHG - dY.

            dltOrg y < 0 ifTrue:[y0 := margin. y := y0]
                        ifFalse:[y1 := margin. y := y1 + h].

            self copyFrom:self x:margin y:y0 toX:margin y:y1 width:innerWT height:h async:false.
            self invalidateX:margin y:y width:innerWT height:(innerHG - h).
        ]
    ] ifFalse:[                                 "/ SCROLL HORIZONTAL
        dX := dX abs.

        innerWT - dX < 20 ifTrue:[
            labelView invalidate.
            self invalidate.
        ] ifFalse:[                             "/ COPY HORIZONTAL
            x0 := x1 := dX + margin.
            w  := width - dX.
            lMargin := labelView margin.

            dltOrg x < 0 ifTrue:[
                " ->"
                x0 := x := margin. 
                self copyFrom:self x:margin y:margin toX:margin+dX y:margin width:innerWT-dX height:innerHG async:false.
                self invalidateX:margin y:margin width:dX height:innerHG.

                labelView notNil ifTrue:[
                    labelView copyFrom:labelView x:lMargin y:lMargin toX:lMargin+dX y:lMargin width:labelView innerWidth-dX height:labelView innerHeight async:false.
                    labelView invalidateX:lMargin y:lMargin width:dX height:labelView innerHeight.
                ].
            ] ifFalse:[
                x1 := margin. x := w.
                self copyFrom:self x:margin+dX y:margin toX:margin y:margin width:innerWT-dX height:innerHG async:false.
                self invalidateX:width-margin-dX y:margin width:dX height:innerHG.

                labelView notNil ifTrue:[
                    labelView copyFrom:labelView x:lMargin+dX y:lMargin toX:lMargin y:lMargin width:labelView innerWidth-dX height:labelView innerHeight async:false.
                    labelView invalidateX:labelView width-lMargin-dX y:lMargin width:dX height:labelView innerHeight.
                ].
            ].
            labelView repairDamage.
        ]
    ].
    self originChanged:dltOrg.

    wg notNil ifTrue:[
        wg processRealExposeEventsFor:self.
    ].

    "Modified: / 7.9.1998 / 16:39:49 / cg"
!

scrollToRowAt:aRowNr colAt:aColNr
    "make row at a row number in column at a column number visible
    "
    |x  "{ Class:SmallInteger }"
     y  "{ Class:SmallInteger }"
     l  "{ Class:SmallInteger }"
     dY "{ Class:SmallInteger }"
     dX "{ Class:SmallInteger }"
    |

    (    (aRowNr between:1 and:(list size))
     and:[aColNr between:0 and:(columnDescriptors size)]
    ) ifFalse:[
        ^ self
    ].

    dY := dX := 0.
    y  := self yVisibleOfRowNr:aRowNr.

    y < margin ifTrue:[
        dY := margin - y.
    ] ifFalse:[
        y := y + rowHeight.
        l := height - margin.
        y > l ifTrue:[dY := l - y]
    ].

    aColNr == 0 ifTrue:[
        dY == 0 ifTrue:[^ self].
        dX := 0.
    ] ifFalse:[
        x  := self xVisibleOfColNr:aColNr.

        x <= margin ifTrue:[
            dX := margin - x
        ] ifFalse:[
            x := x + (columnDescriptors at:aColNr) width.
            l := width - margin.
            x > l ifTrue:[dX := l - x]
        ]
    ].

    (dX == 0 and:[dY == 0]) ifFalse:[
        self scrollTo:(viewOrigin - (dX @ dY)).
    ]


!

scrollToSelection
    "make selection visible
    "
    |rowNr|

    (rowNr := self firstIndexSelected) ~~ 0 ifTrue:[
        self scrollToRowAt:rowNr colAt:selectedColIndex
    ]

!

scrollVerticalTo:aPixelOffset
    "change origin to make aPixelOffset be the top line"

    |orgX orgY|

    orgX := viewOrigin x.
    orgY := (aPixelOffset + rowHeight - 1) // rowHeight * rowHeight.
  ^ self scrollTo:(orgX @ orgY).

!

startAutoScroll:aBlock distance:aDistance
    "setup for auto-scroll (when button-press-moving below view);
     - timeDelta for scroll is computed from distance
    "
    |timeDelta|

    (autoScroll and:[aBlock notNil]) ifFalse:[
        ^ self stopAutoScroll
    ].
    autoScrollBlock notNil ifTrue:[
        Processor removeTimedBlock:autoScrollBlock.
    ] ifFalse:[
        self compressMotionEvents:false.
    ].

    timeDelta := 0.5 / (aDistance abs).

    autoScrollBlock := [
        aBlock value.
        Processor addTimedBlock:autoScrollBlock afterSeconds:timeDelta.
    ].
    Processor addTimedBlock:autoScrollBlock afterSeconds:timeDelta.


!

stopAutoScroll
    "stop any autoScroll
    "
    autoScrollBlock notNil ifTrue:[
        Processor removeTimedBlock:autoScrollBlock.
        autoScrollBlock := nil.
        self compressMotionEvents:true.
    ].


! !

!DSVColumnView methodsFor:'selection'!

addRowToSelection:aRowNr
    "add a row to the selection
     if a column is selected, the column will be closed
    "
    |newSelection|

    self numberOfSelections == 0 ifTrue:[
        self selectColIndex:0 rowIndex:aRowNr.
        ^ self
    ].
    (self isInSelection:aRowNr) ifTrue:[^ self].

    multipleSelectOk ifFalse:[
        self selectColIndex:0 rowIndex:aRowNr.    
        ^ self
    ].
    newSelection := selectedRowIndex copyWith:aRowNr.

    selectedColIndex ~~ 0 ifTrue:[
        self selectColIndex:0 rowIndex:newSelection.
    ] ifFalse:[
        selectedRowIndex := selectedRowIndex copyWith:aRowNr.
        self invalidateRowAt:aRowNr.
        self selectionChanged.
    ].
!

deselect
    "deselect
    "
    self selectColIndex:0 rowIndex:0
!

firstIndexSelected
    "returns index of first row selected or 0
    "
    multipleSelectOk ifFalse:[
        ^ selectedRowIndex
    ].
    selectedRowIndex size ~~ 0 ifTrue:[
        ^ selectedRowIndex at:1
    ].
    ^ 0
!

hasRowSelection
    "returns true if a selection exists, and its a complete row
     (as opposed to either no selection, or a columnSelection)
    "
    ^ self hasSelection and:[selectedColIndex == 0]
!

hasSelection
    "returns true if a selection exists
    "
    ^ self numberOfSelections ~~ 0


!

isInSelection:aRowNr
    "return true, if row, aRowNr is in the selection
    "
    aRowNr ~~ 0 ifTrue:[
        multipleSelectOk ifFalse:[
            ^ aRowNr == selectedRowIndex
        ].
        selectedRowIndex size ~~ 0 ifTrue:[
            ^ selectedRowIndex includes:aRowNr
        ]
    ].
    ^ false
!

isRowSelected:aRowNr
    "return true, if row is in the selection
    "
    selectedColIndex == 0 ifTrue:[
        ^ self isInSelection:aRowNr
    ].
    ^ false
!

isRowVisible:aRowNr
    |y h|

    h := rowHeight.
    y := self yVisibleOfRowNr:aRowNr.

    y < margin ifTrue:[
        (h := h + y) <= margin ifTrue:[
            ^ false
        ].                                            
    ] ifFalse:[
        y >= (height-margin) ifTrue:[^ false].
    ].
    ^ true
!

isSelected:aRowNr inColumn:aColNr
    "returns true if cell in a row; a row number, in a column, a column
     number is selected.
    "
    multipleSelectOk ifFalse:[
        aRowNr ~~ selectedRowIndex ifTrue:[
            ^ false
        ]
    ] ifTrue:[
        (selectedRowIndex size ~~ 0 and:[selectedRowIndex includes:aRowNr]) ifFalse:[
            ^ false
        ]
    ].
    ^ (selectedColIndex == 0 or:[selectedColIndex == aColNr])

!

isSelectionVisibleIn:anExtentPoint
    self selectionIndicesDo:[:selRowNr |
        |y|

        y := self yVisibleOfRowNr:selRowNr.
        (y between:margin and:(anExtentPoint y - margin)) ifTrue:[^ true].
    ].
    ^ false.
!

lastIndexSelected
    "returns index of last row selected or 0
    "
    multipleSelectOk ifFalse:[
        ^ selectedRowIndex
    ].
    selectedRowIndex size ~~ 0 ifTrue:[
        ^ selectedRowIndex last
    ].
    ^ 0
!

makeLineVisible:aLine
    "scroll to make aLine visible
    "
    |colNr|

    aLine == 0 ifTrue:[^ self].
    (self isRowVisible:aLine) ifTrue:[^ self].

    (selectedColIndex notNil and:[selectedColIndex ~~ 0]) ifTrue:[
        colNr := selectedColIndex
    ] ifFalse:[
        colNr := 1
    ].

    self scrollToRowAt:aLine colAt:colNr
!

makeSelectionVisible
    "scroll to make the selection line visible
    "
    |rowNr colNr|

    rowNr := self firstIndexSelected.
    rowNr == 0 ifTrue:[^ self].

    (selectedColIndex notNil and:[selectedColIndex ~~ 0]) ifTrue:[
        colNr := selectedColIndex
    ] ifFalse:[
        colNr := 1
    ].
    self scrollToRowAt:rowNr colAt:colNr
!

maxIndexSelected
    "return the largest index selected or 0
    "
    multipleSelectOk ifFalse:[
        ^ selectedRowIndex
    ].
    selectedRowIndex size ~~ 0 ifTrue:[
        ^ selectedRowIndex max
    ].
    ^ 0
!

minIndexSelected
    "return the smallest index selected or 0
    "
    multipleSelectOk ifFalse:[
        ^ selectedRowIndex
    ].
    selectedRowIndex size ~~ 0 ifTrue:[
        ^ selectedRowIndex min
    ].
    ^ 0
!

numberOfSelections
    "return the number of selected rows
    "
    multipleSelectOk ifFalse:[
        ^ selectedRowIndex ~~ 0 ifTrue:[1] ifFalse:[0]
    ].
    ^ selectedRowIndex size
!

removeRowFromSelection:aRowNr
    "remove a row from the selection
    "
    (self isInSelection:aRowNr) ifFalse:[^ self].

    self numberOfSelections == 1 ifTrue:[
        self deselect.
    ] ifFalse:[
        selectedRowIndex := selectedRowIndex copyWithout:aRowNr.
        self invalidateRowAt:aRowNr.
        self selectionChanged.
    ].
!

selectAllRows
    "select all
    "
    selectedRowIndex isNil ifTrue:[selectedRowIndex := OrderedCollection new].
    1 to:list size do:[:eachRowNr |
        selectedRowIndex add:eachRowNr.    
        self invalidateRowAt:eachRowNr.
    ].
    self selectionChanged.
!

selectColIndex:aColNr rowIndex:aRowNr
    "change selection with notification
    "
    |oC oR|

    oC := self selectedColIndex.
    oR := self selectedRowIndex.

    self setSelectColIndex:aColNr rowIndex:aRowNr.

    (oC ~~ self selectedColIndex or:[oR ~= self selectedRowIndex]) ifTrue:[
        self selectionChanged
    ].
!

selectRow:something
    "select a row
    "
    ^ self selectedRowIndex:something
!

selectRowAt:rowNr colAt:colNr atPoint:aPoint
    |v p|

    self selectColIndex:colNr rowIndex:rowNr.

    ((v := self detectViewAt:aPoint ignoreInvisible:true) notNil 
    and:[v ~~ self])
    ifTrue:[
        p := device translatePoint:aPoint fromView:self toView:v.
        "/ simulate clicking into the editor
        self sensor 
                pushEvent:(WindowEvent buttonPress:#select x:p x y:p y view:v);
                pushEvent:(WindowEvent buttonRelease:#select x:p x y:p y view:v)
    ].
!

selectRowFrom:start to:stop
    |step oldSelection newSelection|

    start == stop ifTrue:[
        self selectColIndex:0 rowIndex:start.
        ^ self
    ].

    step := (start <= stop) ifTrue:1 ifFalse:-1.

    newSelection := (start to:stop by:step) asOrderedCollection.

    selectedColIndex ~~ 0 ifTrue:[
        self selectColIndex:0 rowIndex:newSelection.
        ^ self
    ].

    oldSelection := selectedRowIndex ? #().
    selectedRowIndex := newSelection.

    newSelection 
        select:[:i | (oldSelection includes:i) not]
        thenDo:[:i | self invalidateRowAt:i].

    oldSelection 
        select:[:i | (newSelection includes:i) not]
        thenDo:[:i | self invalidateRowAt:i].
        
    self selectionChanged.
!

selectRowIndex:something
    "set selection of rows
    "
    self selectColIndex:selectedColIndex rowIndex:something


!

selectedColIndex
    "returns selected column number or 0
    "
    ^ selectedColIndex
!

selectedColumn
    "returns selected column or nil
    "
    ^ columnDescriptors at:selectedColIndex ifAbsent:nil.
!

selectedRow
    "returns selected row (or collection if multiple selection) or nil
    "
    multipleSelectOk ifFalse:[
        ^ self at:selectedRowIndex ifAbsent:nil
    ].

    selectedRowIndex size ~~ 0 ifTrue:[
        ^ selectedRowIndex collect:[:i| self at:i]
    ].
    ^ nil
!

selectedRow:something
    "select something
    "
    self selectedRowIndex:something
!

selectedRowIndex
    "returns selected row number or 0
    "
    ^ selectedRowIndex
!

selectedRowIndex:something
    "set the row selection (single or multiple rows);
     does NOT change the selectoed column.
    "
    self selectColIndex:selectedColIndex rowIndex:something
!

selectionChanged
    "selection has changed
    "
    |val|

    model notNil ifTrue:[
        val := self selectedRowIndex copy.

        (model respondsTo:#selectionIndex:) ifTrue:[
            model selectionIndex:val
        ] ifFalse:[
            useIndex ifFalse:[ model value:(self selectedRow) ]
                      ifTrue:[ model value:val ]
        ]
    ].

    actionBlock notNil ifTrue:[
        actionBlock value:(self selectedRowIndex)
    ]
!

selectionIndicesDo:aOneArgBlock
    "evaluate block on each row selected; the argument to the row
     is the index of the selected row
    "
    multipleSelectOk ifFalse:[
        selectedRowIndex ~~ 0 ifTrue:[
            aOneArgBlock value:selectedRowIndex
        ]
    ] ifTrue:[
        selectedRowIndex size ~~ 0 ifTrue:[
            selectedRowIndex do:[:i| aOneArgBlock value:i ]
        ]
    ]
!

setSelectColIndex:colNrArg rowIndex:rowNrArg
    "change selection without notification
    "
    |editSpec rowNr colNr newCol oldCol oldRow sensor sglSelRow oldSz
     keyBrdFwd filter edValue edView|

    rowNr := self validateSelection:rowNrArg.
    colNr := colNrArg.

    (rowNr == 0
    or:[multipleSelectOk and:[rowNr size ~~ 1]]) ifTrue:[
        colNr := 0
    ].

    (colNr := colNr ? 0) ~~ 0 ifTrue:[
        newCol := self columnAt:colNr.
        newCol rendererType == #rowSelector ifTrue:[
            colNr := 0.
            newCol := nil
        ] ifFalse:[
            multipleSelectOk ifTrue:[sglSelRow := rowNr at:1]
                            ifFalse:[sglSelRow := rowNr].

            (newCol canSelect:sglSelRow) ifFalse:[
                newCol := nil.
                colNr  := 0.

                selectRowOnDefault ifFalse:[
                    rowNr  := multipleSelectOk ifFalse:[0] ifTrue:[nil]
                ]
            ]
        ]
    ].

    (rowNr = selectedRowIndex and:[colNr == selectedColIndex]) ifTrue:[
        ^ self
    ].

    "/ release old selection

    oldSz  := self numberOfSelections.
    oldCol := selectedColIndex.
    oldRow := selectedRowIndex.

    "/ cg: must change the selectedRow/Col AFTER we have stored the editValue.
    "/ (otherwise, the editvalue might be stored into the new col as well ...)

"/    selectedRowIndex := rowNr.
"/    selectedColIndex := colNr.

    oldSz == 1 ifTrue:[
        multipleSelectOk ifTrue:[oldRow := oldRow at:1].

        editValue notNil ifTrue:[
            editValue removeDependent:self
        ].
        editView notNil ifTrue:[
            editView allSubViewsDo:[:aSubView|
                (aSubView respondsTo:#accept) ifTrue:[
                    aSubView accept
                ].
            ]
        ].

        editValue notNil ifTrue:[
            edValue := editValue value.
            edValue isSequenceable ifTrue:[
                edValue size == 0 ifTrue:[
                    edValue := nil
                ] ifFalse:[
                    edValue isString ifFalse:[
                        edValue := edValue select:[:el| el notNil ].
                        edValue size == 0 ifTrue:[
                            edValue := nil
                        ]
                    ]
                ]
            ].
            (self columnAt:oldCol) at:oldRow put:edValue.
            modifiedChannel notNil ifTrue:[
                modifiedChannel value:true
            ].
            editValue := nil
        ].
        self destroyEditView.
    ].
    selectedRowIndex := rowNr.
    selectedColIndex := colNr.

    shown ifFalse:[^ self ].

    oldSz > 1 ifTrue:[                                  "/ redraw old selection
        oldRow do:[:aRowNr|                             "/ unselected if visible
            self invalidateRowAt:aRowNr colAt:0
        ]
    ] ifFalse:[
        oldSz == 1 ifTrue:[
            self invalidateRowAt:oldRow colAt:oldCol
        ]
    ].   

    "/ show new selection

    newCol notNil ifTrue:[
        self scrollToRowAt:sglSelRow colAt:colNr.
        editSpec := newCol editorAt:sglSelRow.

        editSpec notNil ifTrue:[
            editView := SimpleView extent:(  (newCol width - (2 * separatorSize)) 
                                           @ (rowHeight    - (2 * separatorSize))
                                          )
                                       in:self.
            self updateEditViewOrigin.

            (newCol containsText or:[newCol showSelectionHighLighted not]) ifTrue:[
                editView viewBackground:(newCol backgroundColorAt:sglSelRow)
            ] ifFalse:[
                editView viewBackground:selectionBackgroundColor
            ].
            edView := editSpec at:1.
            editView add:edView.
            oldSz := editSpec size.

            oldSz == 3 ifTrue:[
                filter := [:aKey| #(#Tab #CursorUp #CursorDown) includes:aKey]
            ] ifFalse:[
                filter := [:aKey| aKey == #Tab]
            ].
            keyBrdFwd := KeyboardForwarder toView:self
                                        condition:nil
                                           filter:filter.

            editView withAllSubViewsDo:[:aView|
                aView delegate:keyBrdFwd.
                aView font:font.
            ].

            (editValue := editSpec at:2 ifAbsent:nil) notNil ifTrue:[
                editValue addDependent:self.
            ].
            editView realize.
            edView canTab:true.
            self windowGroup focusView:edView.

        ] ifFalse:[
            self invalidateRowAt:sglSelRow colAt:colNr
        ].
    ] ifFalse:[
        self selectionIndicesDo:[:i| self invalidateRowAt:i colAt:0 ].
        self scrollToRowAt:(self firstIndexSelected) colAt:0
    ].

    sensor := self sensor.                              "/ catch expose events

    [sensor hasExposeEventFor:nil] whileTrue:[
        self windowGroup processExposeEvents
    ].

    "Modified: / 30.1.2000 / 12:18:25 / cg"
!

validateSelection:aSelection
    |newSel|

    newSel := aSelection.

    (list size == 0 or:[newSel isNil or:[newSel == 0]]) ifTrue:[
        ^ multipleSelectOk ifFalse:[0] ifTrue:[nil]
    ].

    newSel isNumber ifTrue:[
        ^ multipleSelectOk ifFalse:[newSel] ifTrue:[OrderedCollection with:newSel]
    ].
    multipleSelectOk ifFalse:[
        newSel := self identityIndexOfRow:aSelection
    ] ifTrue:[
        newSel := nil.

        aSelection size ~~ 0 ifTrue:[
            aSelection first isNumber ifTrue:[
                newSel := aSelection
            ] ifFalse:[
                newSel := aSelection 
                                collect:[:el | self identityIndexOfRow:el ]
                                thenSelect:[:idx | idx ~~ 0].
                newSel isEmpty ifTrue:[ newSel := nil ].
            ]
        ]
    ].
    ^ newSel
! !

!DSVColumnView class methodsFor:'documentation'!

version
    ^ '$Header: /cvs/stx/stx/libwidg2/DSVColumnView.st,v 1.171 2003-07-04 08:05:19 tm Exp $'
! !