"
COPYRIGHT (c) 1999 by eXept Software AG
All Rights Reserved
This software is furnished under a license and may be used
only in accordance with the terms of that license and with the
inclusion of the above copyright notice. This software may not
be provided or otherwise made available to, or used by, any
other person. No title to or ownership of the software is
hereby transferred.
"
"{ Package: 'stx:libwidg2' }"
ListModelView subclass:#SelectionInListModelView
instanceVariableNames:'selection multipleSelectOk selectOnButtomMenu actionBlock
doubleClickActionBlock selectConditionBlock clickLine
highlightMode useIndex ignoreReselect toggleSelect hilightFgColor
hilightBgColor hilightLevel hilightFrameColor hilightStyle
dragAccessPoint dropTarget dropSource editorView openEditorAction
closeEditorAction highlightEnterItem enterItem'
classVariableNames:'DefaultHilightStyle DefaultHilightBackgroundColor
DefaultHilightForegroundColor DefaultHilightLevel
DefaultHilightFrameColor'
poolDictionaries:''
category:'Views-Lists'
!
!SelectionInListModelView class methodsFor:'documentation'!
copyright
"
COPYRIGHT (c) 1999 by eXept Software AG
All Rights Reserved
This software is furnished under a license and may be used
only in accordance with the terms of that license and with the
inclusion of the above copyright notice. This software may not
be provided or otherwise made available to, or used by, any
other person. No title to or ownership of the software is
hereby transferred.
"
!
documentation
"
SelectionInListModelView is mostly like SelectionInListView,
but derives from the ListModelView and thus the list is kept
by the model.
[Instance variables:]
selection <misc> the current selection. nil, a number or collection of numbers
multipleSelectOk <Boolean> allow/disallow multiple selections( default:false )
selectOnButtomMenu <Boolean> enable/disable selection will change on menu pressed
actionBlock <Block> action evaluated on single click
doubleClickActionBlock <Block> action evaluated on double click
selectConditionBlock
clickLine <Number> clicked line during button motion
useIndex <Boolean> representation of the model selection
ignoreReselect <Boolean> if set, a click on an already selected entry is ignored
toggleSelect <Boolean> a click on an entry unselects it and vice versa
highlightMode <Symbol> how to draw the selection
hilightFgColor <Color> foregroundColor of highlighted items
hilightBgColor <Color> backgroundColor of highlighted items
hilightLevel <Integer> level to draw selections (i.e. for 3D effect)
hilightFrameColor <Color> rectangle around highlighted items
hilightStyle <Boolean> actions on widget are enabled/disabled
dragAccessPoint <Point> point where the drag operation starts from
dropTarget <DropTarget> keeps information about the drop operation
dropSource <DropSource> keeps information about the drag operation
editorView <View> editor on current selected item
openEditorAction <Action> action to get an editor on the current selection from user
closeEditorAction <Action> action invoked before the editor is closed.
enterItem <Item/nil> item over which the mouse pointer is located
or nil
highlightEnterItem <Boolean> enable or disable highlight of enterItem
[author:]
Claus Atzkern
[see also:]
ListModelView
HierarchicalListView
"
!
examples
"
[exBegin]
|top list view|
list := List new.
1 to:100 do:[:i| list add:('element: ', i printString) ].
top := StandardSystemView new; extent:300@300.
view := ScrollableView for:SelectionInListModelView miniScroller:true
origin:0.0@0.0 corner:1.0@1.0 in:top.
view list:list.
top open.
[exEnd]
[exBegin]
|top list view|
list := List new.
1 to:100 do:[:i| list add:('element: ', i printString) ].
top := StandardSystemView new; extent:300@300.
view := ScrollableView for:SelectionInListModelView miniScroller:true
origin:0.0@0.0 corner:1.0@1.0 in:top.
view list:list.
view openEditorAction:[:ln :aGC| |field|
field := EditField new.
field level:0.
field acceptOnLostFocus:true.
field acceptAction:[:x| list at:ln put:(field contents) ].
field font:(aGC font).
field contents:(list at:ln).
field
].
top open.
[exEnd]
[exBegin]
|top list view item|
list := HierarchicalList new.
item := HierarchicalItem::Example labeled:'Test'.
item expand.
list showRoot:false.
list root:item.
top := StandardSystemView new; extent:300@300.
view := ScrollableView for:SelectionInListModelView miniScroller:true
origin:0.0@0.0 corner:1.0@1.0 in:top.
view list:list.
view doubleClickAction:[:i| (list at:i) toggleExpand ].
top open.
[exEnd]
"
! !
!SelectionInListModelView class methodsFor:'defaults'!
updateStyleCache
"extract values from the styleSheet and cache them in class variables"
<resource: #style (
#'selection.hilightForegroundColor' #'selection.hilightBackgroundColor'
#'selection.hilightFrameColor' #'selection.hilightLevel'
#'selection.foregroundColor' #'selection.backgroundColor'
#'selection.shadowColor' #'selection.lightColor'
#'selection.font' #'selection.hilightStyle'
#'text.foregroundColor'
)>
DefaultHilightForegroundColor := StyleSheet colorAt:'selection.hilightForegroundColor'.
DefaultHilightBackgroundColor := StyleSheet colorAt:'selection.hilightBackgroundColor'.
DefaultHilightFrameColor := StyleSheet colorAt:'selection.hilightFrameColor'.
DefaultHilightLevel := StyleSheet at:'selection.hilightLevel' default:0.
DefaultHilightStyle := StyleSheet at:'selection.hilightStyle' default:(StyleSheet name).
DefaultForegroundColor := StyleSheet colorAt:'selection.foregroundColor'.
DefaultBackgroundColor := StyleSheet colorAt:'selection.backgroundColor'.
DefaultShadowColor := StyleSheet colorAt:'selection.shadowColor'.
DefaultLightColor := StyleSheet colorAt:'selection.lightColor'.
DefaultForegroundColor isNil ifTrue:[
DefaultForegroundColor := StyleSheet colorAt:'text.foregroundColor' default:Black
].
"
self updateStyleCache
"
! !
!SelectionInListModelView methodsFor:'accessing'!
list:aList
"get the status of <showRoot> from the list
"
self deselectWithoutRedraw.
^ super list:aList
! !
!SelectionInListModelView methodsFor:'accessing editor'!
closeEditor
"close the current editor
"
|editor|
(editor := editorView) notNil ifTrue:[
editorView := nil.
closeEditorAction notNil ifTrue:[
closeEditorAction value:editor.
].
editor destroy.
].
!
closeEditorAction
"get the block which is evaluated before the editor is closed; the argument
to the action is the editor.
"
^ closeEditorAction
!
closeEditorAction:anOneArgAction
"get the block which is evaluated before the editor is closed; the argument
to the action is the editor.
"
closeEditorAction := anOneArgAction.
!
openEditor
"opens the editor on the current selection;
returns the editorView or nil if no openEditorAction is defined
or no single selection exists ...
"
|numArgs lnNr|
self closeEditor.
shown ifFalse:[^ nil].
openEditorAction isNil ifTrue:[^ nil].
lnNr := self selectedIndex.
lnNr == 0 ifTrue:[^ nil].
self makeSelectionVisible.
numArgs := openEditorAction numArgs.
numArgs == 0 ifTrue:[
editorView := openEditorAction value
] ifFalse:[
numArgs == 1 ifTrue:[editorView := openEditorAction value:lnNr]
ifFalse:[editorView := openEditorAction value:lnNr value:self]
].
editorView isNil ifTrue:[^ nil].
editorView superView isNil ifTrue:[
self addSubView:editorView
].
self computeEditorLayout.
editorView realize.
self windowGroup focusView:editorView.
^ editorView
!
openEditorAction
"get the block which is evaluated to get the editor which is set for the
line; the arguments to the block is the line number and the widget itself
"
^ openEditorAction
!
openEditorAction:aTwoArgAction
"set the block which is evaluated to get the editor which is set for the
line; the arguments to the block is the line number and the widget itself
"
openEditorAction := aTwoArgAction.
! !
!SelectionInListModelView methodsFor:'accessing-actions'!
action
"get the action block to be performed on select
"
^ actionBlock
!
action:aOneArgAction
"set the action block to be performed on select
"
actionBlock := aOneArgAction
!
doubleClickAction
"get the action block to be performed on doubleclick
"
^ doubleClickActionBlock
!
doubleClickAction:aOneArgAction
"set the action block to be performed on doubleclick
"
doubleClickActionBlock := aOneArgAction
!
selectConditionBlock
"set the conditionBlock; this block is evaluated before a selection
change is performed; the change will not be done, if the evaluation
returns false
"
^ selectConditionBlock
!
selectConditionBlock:aOneArgBlock
"set the conditionBlock; this block is evaluated before a selection
change is performed; the change will not be done, if the evaluation
returns false
"
selectConditionBlock := aOneArgBlock.
! !
!SelectionInListModelView methodsFor:'accessing-behavior'!
highlightEnterItem
"enable or disable to highlight the item over which the mouse pointer is located
"
^ highlightEnterItem
!
highlightEnterItem:aBool
"enable or disable to highlight the item over which the mouse pointer is located
"
highlightEnterItem ~~ aBool ifTrue:[
highlightEnterItem := aBool.
self pointerEntersItem:nil.
].
!
highlightMode
"get the mode how to draw a selected line:
#line draw whole line selected
#label draw label selected
"
^ highlightMode
!
highlightMode:aMode
"set the mode how to draw a selected line:
#line draw whole line selected
#label draw label selected
"
highlightMode := aMode.
!
ignoreReselect
"get the ignoreReselect flag - see method #ignoreReselect: for more details
"
^ ignoreReselect
!
ignoreReselect:aBoolean
"set/clear the ignoreReselect flag -
if set, a click on an already selected entry is ignored.
Otherwise the notification is done, even if no
change in the selection occurs.
(for example, in browser to update a method).
Setting ignoreReselect to false makes sense if data is shown
which may change by itself (i.e. without the user doing anything)
For example, the inspector uses this, and redisplays the value,
if the selection is the same.
The default is true, meaning that a click on an already selected
does not lead to a notification via the actionBlock/change mechanism.
"
ignoreReselect := aBoolean
!
multipleSelectOk
"allow/disallow multiple selections; the default is false
"
^ multipleSelectOk
!
multipleSelectOk:aState
"allow/disallow multiple selections; the default is false
"
multipleSelectOk := aState ? false.
!
selectOnButtomMenu
"define the button-menu-press behavior; if true the line under the mouse
will be selected before the menu is opened. Otherwise the menu is opened
on the current selection.
"
^ selectOnButtomMenu
!
selectOnButtomMenu:aBoolean
"define the button-menu-press behavior; if true the line under the mouse
will be selected before the menu is opened. Otherwise the menu is opened
on the current selection.
"
selectOnButtomMenu := aBoolean.
!
toggleSelect
"get the toggleSelect flag - see method #toggleSelect: for more details
"
^ toggleSelect
!
toggleSelect:aBoolean
"turn on/off toggle select. If true, clicking on a selected entry
unselects it and vice versa. The default is false, which means
that clicking on an already selected entry does not change its
select status (see also ignoreReselect:).
"
toggleSelect := aBoolean.
!
useIndex
"set/clear the useIndex flag.
the selection writen to the model are the indices into the list
or the elements selected.
"
^ useIndex
!
useIndex:aBoolean
"set/clear the useIndex flag.
the selection writen to the model are the indices into the list
or the elements selected.
"
useIndex := aBoolean ? true
! !
!SelectionInListModelView methodsFor:'change & update'!
argForChangeMessage
"return the argument for a selectionChange;
depending on the setting of useIndex, this is either the numeric
index of the selection or the value (i.e. the string)
"
selection isNil ifTrue:[
multipleSelectOk ifTrue:[^ #()].
^ useIndex ifTrue:[0] ifFalse:[nil]
].
useIndex ifTrue:[^ selection].
multipleSelectOk ifFalse:[
^ list at:selection ifAbsent:nil
].
^ selection collect:[:nr| list at:nr ifAbsent:nil ]
!
contentsChanged
super contentsChanged.
self computeEditorLayout.
!
lineChangedAt:aLnNr with:arg
super lineChangedAt:aLnNr with:arg.
(editorView notNil and:[aLnNr == self selectedIndex]) ifTrue:[
self computeEditorLayout.
]
!
listSizeChanged:aLnNr nLines:aDeltaLines
"update selection
"
|changed cnts|
super listSizeChanged:aLnNr nLines:aDeltaLines.
selection isNil ifTrue:[^ self].
list size == 0 ifTrue:[
selection := nil.
^ self selectionChanged
].
multipleSelectOk ifFalse:[
selection < aLnNr ifTrue:[^ self].
selection := selection + aDeltaLines.
(aDeltaLines < 0 and:[selection < aLnNr]) ifTrue:[
selection := nil.
^ self selectionChanged
]
] ifTrue:[
changed := false.
aDeltaLines < 0 ifFalse:[
selection keysAndValuesDo:[:i :ln|
ln >= aLnNr ifTrue:[
changed := true.
selection at:i put:(ln + aDeltaLines)
]
]
] ifTrue:[
cnts := 0.
selection keysAndValuesDo:[:i :ln||new|
ln >= aLnNr ifTrue:[
changed := true.
(new := ln + aDeltaLines) < aLnNr ifTrue:[
cnts := cnts + 1.
new := nil
].
selection at:i put:new
]
].
cnts ~~ 0 ifTrue:[
cnts == selection size ifTrue:[
selection := nil
] ifFalse:[
selection := selection select:[:ln| ln notNil]
].
^ self selectionChanged.
].
].
changed ifFalse:[^ self].
].
(useIndex and:[model notNil]) ifTrue:[
model setValue:(self argForChangeMessage)
].
!
originChanged:delta
"setup the origin of the editing view
"
self computeEditorLayout.
super originChanged:delta.
!
selectionChanged
"selection has changed. Call actionblock and/or send changeMessage if defined
"
|arg|
self closeEditor.
(model isNil and:[actionBlock isNil]) ifTrue:[
^ self
].
arg := self argForChangeMessage.
model notNil ifTrue:[
model removeDependent:self.
"/ change models value to force a change notification: reselect mode
arg = model value ifTrue:[
model setValue:(arg isNil ifTrue:[0] ifFalse:[nil]).
].
self sendChangeMessage:#value: with:arg.
model notNil ifTrue:[ "/ argggh could be nilled
model addDependent:self.
]
].
actionBlock notNil ifTrue:[
actionBlock valueWithOptionalArgument:arg
].
!
updateFromModel
"update selection from the model
"
|value nsel sensor|
model isNil ifTrue:[
^ self
].
( multipleSelectOk
and:[clickLine notNil
and:[(sensor := self sensor) notNil
and:[sensor leftButtonPressed]]]
) ifTrue:[
"running in button motion; discard change notification
"
^ self
].
value := model value.
(useIndex or:[value isNil or:[value isNumber]]) ifFalse:[
multipleSelectOk ifFalse:[
value := list identityIndexOf:value
] ifTrue:[
value isEmpty ifTrue:[
value := nil
] ifFalse:[
nsel := OrderedCollection new.
value do:[:e||i|(i := list identityIndexOf:e) ~~ 0 ifTrue:[nsel add:i]].
value := nsel notEmpty ifTrue:[nsel] ifFalse:[nil]
]
].
].
self setSelection:value.
! !
!SelectionInListModelView 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
"start drag at a point
"
dropSource notNil ifTrue:[
dropSource startDragSelector notNil ifTrue:[
dropSource startDragIn:self at:aPoint
] ifFalse:[
DragAndDropManager new startDragFrom:self
dropSource:dropSource
offset:#center
]
]
! !
!SelectionInListModelView methodsFor:'drawing'!
drawFrom:start to:stop x:x y:y w:w
"draw the lines between start to stop without clearing the background
"
highlightMode notNil ifTrue:[
self selectionDo:[:lnNr|
(lnNr between:start and:stop) ifTrue:[
self drawSelectionFrameAt:lnNr x:x w:w
]
]
].
self drawElementsFrom:start to:stop x:x y:y w:w.
!
drawLabelAt:anIndex x:x y:y h:h
"draw the label at position x/y without clearing the background
"
|item y0 x1|
editorView notNil ifTrue:[
"/ there is an open editor for the line; thus no redraw for the label (hidden by editor)
self selectedIndex == anIndex ifTrue:[^ self].
].
item := list at:anIndex ifAbsent:nil.
item notNil ifTrue:[
(highlightMode notNil and:[self isInSelection:anIndex]) ifTrue:[
self paint:hilightFgColor on:hilightBgColor
] ifFalse:[
enterItem == item ifTrue:[
self paint:hilightBgColor on:bgColor.
] ifFalse:[
self paint:fgColor on:bgColor.
].
].
self displayElement:item x:x y:y h:h.
enterItem == item ifTrue:[
y0 := y + h - 2.
x1 := x + (item widthOn:self).
self displayLineFromX:x y:y0 toX:x1 y:y0.
].
]
!
drawSelectionFrameAt:lnNr x:x w:w
"draw the background and foreground of the selection frame
at a lineNr.
"
|item
x0 "{ Class:SmallInteger }"
x1 "{ Class: SmallInteger }"
y0 "{ Class:SmallInteger }"
y1 "{ Class:SmallInteger }"
hL "{ Class:SmallInteger }"
wL "{ Class:SmallInteger }"
|
editorView notNil ifTrue:[
"/ there is an open editor; do not redraw selected
^ self
].
(item := list at:lnNr ifAbsent:nil) isNil ifTrue:[
"/ list might change during drawing; item no longer visible
^ self
].
"/ CLEAR THE BACKGROUND
y0 := self yVisibleOfLine:lnNr.
y1 := self yVisibleOfLine:(lnNr + 1).
hL := y1 - y0.
highlightMode == #line ifTrue:[
x0 := x.
x1 := x0 + w.
] ifFalse:[
highlightMode == #label ifFalse:[^ self].
x0 := (self xVisibleOfItem:item) - (textStartLeft // 2).
x1 := x0 + (item widthOn:self) + textStartLeft.
].
wL := x1 - x0.
wL > 0 ifFalse:[^ self].
self paint:hilightBgColor.
self fillRectangleX:x0 y:y0 width:wL height:hL.
"/ DRAW THE FRAME
hilightFrameColor notNil ifTrue:[
hilightLevel == 0 ifTrue:[
self paint:hilightFrameColor.
highlightMode == #line ifTrue:[
self displayLineFromX:x0 y:y0 toX:x1 y:y0.
y1 := y0 + hL - 1.
self displayLineFromX:x0 y:y1 toX:x1 y:y1.
] ifFalse:[
self displayRectangleX:x0 y:y0 width:wL height:hL
].
^ self.
]
] ifFalse:[
hilightStyle == #motif ifTrue:[
self paint:bgColor.
y1 := y0 + 1.
highlightMode == #line ifTrue:[
self displayLineFromX:x0 y:y1 toX:x1 y:y1.
y1 := y0 + hL - 2.
self displayLineFromX:x0 y:y1 toX:x1 y:y1.
] ifFalse:[
self displayRectangleX:x0 + 1 y:y1 width:wL - 2 height:hL - 2
]
]
].
hilightLevel ~~ 0 ifTrue:[
"/ draw edge
highlightMode == #line ifTrue:[
x0 := margin.
wL := width - x0 - x0.
].
self drawEdgesForX:x0 y:y0 width:wL height:hL level:hilightLevel.
]
!
invalidateSelectionAt:aLineNr
"redraw a line which changed its selection status.
optimized when drawing only the label.
"
|item x|
editorView notNil ifTrue:[
"/ there is an open editor; do not redraw selected
^ self
].
(shown and:[aLineNr notNil and:[highlightMode notNil]]) ifFalse:[
^ self
].
highlightMode == #label ifTrue:[
item := list at:aLineNr ifAbsent:nil.
item isNil ifTrue:[
^ self
].
x := (self xVisibleOfItem:item) - (textStartLeft // 2)
] ifFalse:[
x := 0.
].
self invalidateLineAt:aLineNr fromX:x
! !
!SelectionInListModelView methodsFor:'event handling'!
buttonMotion:buttonMask x:x y:y
"mouse-move while button was pressed - handle selection changes
"
|dragPoint lnNr d0 nsel viewOrgY yAbsLine item
step "{ Class:SmallInteger }"
|
enabled ifFalse:[^ self].
clickLine isNil ifTrue:[
highlightEnterItem ifTrue:[
self sensor anyButtonPressed ifFalse:[
lnNr := self yVisibleToLineNr:y.
lnNr notNil ifTrue:[ item := list at:lnNr ifAbsent:nil ]
ifFalse:[ item := nil ].
self pointerEntersItem:item.
]
].
^ self
].
self hasSelection ifFalse:[^ self].
dragAccessPoint notNil ifTrue:[
dragPoint := x @ y.
(dragAccessPoint dist:dragPoint) > 5.0 ifTrue:[
dragAccessPoint := nil.
self startDragAt:dragPoint
].
^ self
].
(multipleSelectOk and:[self sensor leftButtonPressed]) ifFalse:[
^ self
].
(lnNr := self yVisibleToLineNr:y) isNil ifTrue:[
lnNr := list size
] ifFalse:[
lnNr := lnNr max:1
].
lnNr == clickLine ifTrue:[^ self].
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).
].
].
"move inside - stop autoscroll if any"
self stopAutoScroll.
nsel := OrderedCollection new.
step := lnNr > clickLine ifTrue:[1] ifFalse:[-1].
clickLine to:lnNr by:step do:[:i|
((self isInSelection:i) or:[self canSelectIndex:i forAdd:true]) ifTrue:[nsel add:i].
].
(self selectWithoutScroll:nsel redraw:true) ifFalse:[^ self].
viewOrgY := viewOrigin y.
yAbsLine := self yAbsoluteOfLine:lnNr.
yAbsLine < viewOrgY ifTrue:[
self scrollTo:( viewOrigin x @ yAbsLine) redraw:true
] ifFalse:[
yAbsLine - viewOrgY > height ifTrue:[
d0 := yAbsLine - height.
self scrollTo:( viewOrigin x @ d0) redraw:true.
].
].
self repairDamage.
self selectionChanged.
!
buttonMultiPress:button x:x y:y
"button was pressed multiple - handle a doubleClick action
"
clickLine := nil.
dragAccessPoint := nil.
enabled ifFalse:[^ self].
((button == 1) or:[button == #select]) ifFalse:[
^ super buttonMultiPress:button x:x y:y
].
self doubleClicked
!
buttonPress:button x:x y:y
"a button was pressed - handle selection here
"
|sensor nsel start step item menu appl isInSelection lineNr|
clickLine := nil.
dragAccessPoint := nil.
self pointerEntersItem:nil.
enabled ifFalse:[^ self].
sensor := self sensor.
sensor notNil ifTrue:[ lineNr := self yVisibleToLineNr:y ]
ifFalse:[ lineNr := nil ].
((button == 2) or:[button == #menu]) ifTrue:[
selectOnButtomMenu ifTrue:[
( lineNr notNil
and:[lineNr ~~ self selectedIndex
and:[self canSelectIndex:lineNr forAdd:false ]]
) ifTrue:[
(self selectWithoutScroll:lineNr redraw:true) ifTrue:[
self selectionChanged
].
item := self selectedElement.
] ifFalse:[
item := nil
]
] ifFalse:[
item := self selectedElement.
].
item notNil ifTrue:[
self makeSelectionVisible.
menu := item perform:#middleButtonMenu ifNotUnderstood:nil.
menu notNil ifTrue:[
menu isCollection ifTrue:[
menu := Menu new fromLiteralArrayEncoding:menu.
appl := self application.
appl notNil ifTrue:[
menu findGuiResourcesIn:appl.
"/ menu receiver:appl -- now done in findGuiResources ...
] ifFalse:[
menu receiver:item
]
].
menu startUp.
^ self
]
].
super buttonPress:button x:x y:y.
^ self
].
(clickLine := lineNr) isNil ifTrue:[ ^ self ].
isInSelection := self isInSelection:clickLine.
multipleSelectOk ifTrue:[
sensor ctrlDown ifTrue:[
(isInSelection or:[self canSelectIndex:clickLine forAdd:true]) ifTrue:[
self toggleSelection:clickLine.
self selectionChanged
].
^ self
].
(self canSelectIndex:clickLine forAdd:sensor shiftDown) ifFalse:[^ self].
(sensor shiftDown and:[(start := self firstInSelection) notNil]) ifTrue:[
step := clickLine < start ifTrue:[-1] ifFalse:[1].
nsel := OrderedCollection new.
start to:clickLine by:step do:[:i|
((self isInSelection:i) or:[self canSelectIndex:i forAdd:true]) ifTrue:[nsel add:i]
].
(self selectWithoutScroll:nsel redraw:true) ifTrue:[
self selectionChanged
].
^ self
]
] ifFalse:[
(self canSelectIndex:clickLine forAdd:false) ifFalse:[^ self]
].
(isInSelection and:[self canDrag]) ifTrue:[
dragAccessPoint := x @ y.
] ifFalse:[
self buttonPressOrReleaseAtLine:clickLine x:x y:y
].
!
buttonPressOrReleaseAtLine:aLnNr x:x y:y
"handle a button press or release at a line
"
aLnNr == self selectedIndex ifTrue:[
editorView notNil ifTrue:[^ self].
openEditorAction notNil ifTrue:[
self openEditorAtX:x y:y.
editorView notNil ifTrue:[^ self].
].
ignoreReselect ifFalse:[
(toggleSelect and:[self sensor ctrlDown]) ifTrue:[
self selection:nil
] ifFalse:[
self selectionChanged
].
].
] ifFalse:[
(self selectWithoutScroll:aLnNr redraw:true) ifTrue:[
self selectionChanged
]
].
!
buttonRelease:button x:x y:y
"a button was released
"
|dragPoint clickLnNr|
dragPoint := dragAccessPoint.
clickLnNr := clickLine.
clickLine := dragAccessPoint := nil.
self stopAutoScroll.
(enabled and:[dragPoint notNil and:[clickLnNr notNil]]) ifTrue:[
self buttonPressOrReleaseAtLine:clickLnNr x:(dragPoint x) y:(dragPoint y).
].
!
characterPress:aKey x:x y:y
" a character is pressed - lookup and change selection
"
|lnNr size idx sensor stp to1 fr2|
(enabled and:[(size := self size) > 1]) ifFalse:[
^ self
].
lnNr := self firstInSelection ? 0.
((sensor := self sensor) notNil and:[sensor shiftDown]) ifTrue:[
stp := -1. "/ search backward
to1 := 1.
fr2 := size.
] ifFalse:[
stp := 1. "/ search forward
to1 := size.
fr2 := 1.
].
idx := self findLineFrom:lnNr+stp to:to1 by:stp startingWithCharacter:aKey.
idx == 0 ifTrue:[
idx := self findLineFrom:fr2 to:lnNr-stp by:stp startingWithCharacter:aKey
].
idx ~~ 0 ifTrue:[^ self selection:idx]
!
characterSearchItemStringAt:anIndex
|item|
item := list at:anIndex ifAbsent:nil.
item isNil ifTrue:[^ item].
^ item perform:#string ifNotUnderstood:nil.
!
containerChangedSize
"/ stupid kludge for motif-style (which draws a frame at the right-edge)
"/ cg: I think it should not (see normal SelectionInListView, which does only
"/ draw the frame at the top and bottom, but NOT at the left and right
selection notNil ifTrue:[
(hilightFrameColor notNil
or:[hilightStyle == #motif
or:[hilightLevel ~~ 0]]) ifTrue:[
"/ invalidate the right-edge
self invalidate:(((width-3) @ 0) corner:((width-1) @ (height-1))).
]
].
super containerChangedSize.
!
doubleClicked
"handle a double click
"
(doubleClickActionBlock notNil and:[self numberOfSelections == 1]) ifTrue:[
(doubleClickActionBlock numArgs == 1) ifTrue:[
doubleClickActionBlock value:(self selectedIndex)
] ifFalse:[
doubleClickActionBlock value
]
]
!
findLineFrom:aStart to:aStop by:aStep startingWithCharacter:aCharacter
"find a line starting with a character
"
|item char lbl cmp
size "{ Class:SmallInteger }"
start "{ Class:SmallInteger }"
stop "{ Class:SmallInteger }"
|
(size := list size) ~~ 0 ifTrue:[
aStep > 0 ifTrue:[
aStart > aStop ifTrue:[^ 0].
] ifFalse:[
(aStep == 0 or:[aStop > aStart]) ifTrue:[^ 0]
].
start := aStart < 0 ifTrue:[1] ifFalse:[aStart min:size].
stop := aStop < 0 ifTrue:[1] ifFalse:[aStop min:size].
char := aCharacter asUppercase.
start to:stop by:aStep do:[:anIndex|
(self canSelectIndex:anIndex forAdd:false) ifTrue:[
lbl := self characterSearchItemStringAt:anIndex.
lbl notNil ifTrue:[
cmp := lbl string at:1 ifAbsent:nil.
cmp notNil ifTrue:[
(char == cmp or:[char == cmp asUppercase]) ifTrue:[
^ anIndex
]
]
]
]
]
].
^ 0
!
keyPress:aKey x:x y:y
"a key was pressed - handle page-keys here
"
<resource: #keyboard( #Return #CursorUp #CursorDown )>
|sensor n size lineNr delta|
enabled ifFalse:[
^ super keyPress:aKey x:x y:y
].
aKey == #Return ifTrue:[
self numberOfSelections == 1 ifTrue:[self doubleClicked].
^ self
].
aKey isCharacter ifTrue:[
^ self characterPress:aKey x:x y:y
].
(aKey == #CursorUp or:[aKey == #CursorDown]) ifFalse:[
^ super keyPress:aKey x:x y:y
].
(size := self size) == 0 ifTrue:[
^ self
].
lineNr := self selectedIndex.
sensor := self sensor.
sensor notNil ifTrue:[
n := (1 + (sensor compressKeyPressEventsWithKey:aKey)) \\ size.
n == 0 ifTrue:[n := 1].
] ifFalse:[
n := 1
].
aKey == #CursorUp ifTrue:[
lineNr == 0 ifTrue:[lineNr := size + 1].
(n := lineNr - n) <= 0 ifTrue:[n := size + n].
lineNr > size ifTrue:[lineNr := size].
delta := -1.
] ifFalse:[
(n := lineNr + n) > size ifTrue:[n := n - size].
lineNr == 0 ifTrue:[lineNr := 1].
delta := 1.
].
"/ TODO: care for shift-Down (add-to-selection)
[
self canSelectIndex:n forAdd:false
] whileFalse:[
n := n + delta.
n > size ifTrue:[n := 1].
n == 0 ifTrue:[n := size].
n == lineNr ifTrue:[^ self]
].
self selection:n
!
pointerEntersItem:anItemOrNil
"the pointer moves over an item or nil
"
|lnNr x newItem|
(shown and:[list size ~~ 0]) ifFalse:[
enterItem := nil.
^ self.
].
highlightEnterItem ifTrue:[ newItem := anItemOrNil ]
ifFalse:[ newItem := nil ].
anItemOrNil == enterItem ifTrue:[ ^ self ].
2 timesRepeat:[
enterItem notNil ifTrue:[
lnNr := list identityIndexOf:enterItem.
lnNr notNil ifTrue:[
x := self xVisibleOfItem:enterItem.
self invalidateLineAt:lnNr fromX:x
].
].
"/ set the new item
enterItem := anItemOrNil.
].
!
pointerLeave:state
self pointerEntersItem:nil.
super pointerLeave:state.
!
sizeChanged:how
|selectionWasVisible|
selectionWasVisible := false.
"/ stupid kludge for motif-style (which draws a frame at the right-edge)
"/ cg: I think it should not (see normal SelectionInListView, which does only
"/ draw the frame at the top and bottom, but NOT at the left and right
selection notNil ifTrue:[
selectionWasVisible := self isSelectionVisibleIn:(previousExtent ? self extent).
(hilightFrameColor notNil
or:[hilightStyle == #motif
or:[hilightLevel ~~ 0]]) ifTrue:[
"/ invalidate the right-edge
self invalidate:(((width-3) @ 0) corner:((width-1) @ (height-1))).
].
].
super sizeChanged:how.
selectionWasVisible ifTrue:[
self makeSelectionVisible
].
! !
!SelectionInListModelView methodsFor:'focus handling'!
wantsFocusWithPointerEnter
"return true, if I want the focus when
the mouse pointer enters"
|pref|
pref := UserPreferences current focusFollowsMouse.
(pref ~~ false
and:[(styleSheet at:#'selection.requestFocusOnPointerEnter' default:true)
]) ifTrue:[
list size > 0 ifTrue:[
^ true
]
].
^ false
! !
!SelectionInListModelView methodsFor:'initialize / release'!
fetchResources
"fetch device colors and ..., to avoid reallocation at redraw time;
*** called after a create or snapin to fetch all device resources
"
super fetchResources.
hilightFgColor := self colorOnDevice:hilightFgColor.
hilightBgColor := self colorOnDevice:hilightBgColor.
hilightFrameColor := self colorOnDevice:hilightFrameColor.
!
initStyle
"setup viewStyle specifics
"
super initStyle.
hilightFrameColor := nil.
hilightStyle := DefaultHilightStyle.
highlightMode := #label.
textStartLeft := 4.
selectOnButtomMenu := styleSheet at:#'selection.selectOnButtomMenu' default:false.
super font:(styleSheet fontAt:#'selection.font').
device hasGrayscales ifTrue:[
"
must get rid of these hard codings
"
(hilightStyle == #next) ifTrue:[
hilightFgColor := fgColor.
hilightBgColor := White.
hilightFrameColor := fgColor
] ifFalse:[
(hilightStyle == #motif) ifTrue:[
fgColor := White.
bgColor := Grey.
viewBackground := bgColor.
hilightFgColor := bgColor.
hilightBgColor := fgColor.
] ifFalse:[
(hilightStyle == #openwin) ifTrue:[
hilightFgColor := fgColor.
hilightBgColor := Color grey.
]
]
]
].
hilightFgColor isNil ifTrue:[
hilightFgColor := bgColor.
].
hilightBgColor isNil ifTrue:[
hilightBgColor := fgColor.
].
DefaultForegroundColor notNil ifTrue:[
fgColor := DefaultForegroundColor
].
DefaultBackgroundColor notNil ifTrue:[
bgColor := viewBackground := DefaultBackgroundColor
].
DefaultHilightForegroundColor notNil ifTrue:[
hilightFgColor := DefaultHilightForegroundColor
].
DefaultHilightBackgroundColor notNil ifTrue:[
hilightBgColor := DefaultHilightBackgroundColor
].
DefaultHilightFrameColor notNil ifTrue:[
hilightFrameColor := DefaultHilightFrameColor
].
hilightLevel := DefaultHilightLevel ? 0.
lineSpacing := (hilightLevel abs > 0) ifTrue:[3] ifFalse:[2].
hilightFgColor isNil ifTrue:[
hilightFgColor := bgColor.
hilightBgColor := fgColor
].
!
initialize
"setup default attributes/behavior
"
multipleSelectOk := false.
useIndex := true.
ignoreReselect := true.
toggleSelect := false.
highlightEnterItem := false.
super initialize.
!
mapped
"get selection from model; scroll to selection
"
super mapped.
self makeSelectionVisible.
!
realize
"get selection from model; scroll to selection
"
self updateFromModel.
super realize.
! !
!SelectionInListModelView methodsFor:'private editor'!
computeEditorLayout
"update the layout of the editor
"
|lnNr item y x h|
editorView isNil ifTrue:[
^ self
].
( (lnNr := self selectedIndex) == 0
or:[(item := list at:lnNr ifAbsent:nil) isNil]
) ifTrue:[
"/ there is no more single selection; thus close the editor
^ self closeEditor
].
y := self yVisibleOfLine:lnNr.
x := (self xVisibleOfItem:item) - (textStartLeft // 2).
h := self yVisibleOfLine:(lnNr + 1).
editorView layout:( Rectangle left:x top:y right:(width - 1 - margin) bottom:(h - 1) ).
!
openEditorAtX:x y:y
"opens an editor on the current single selection
"
|item lnNr x0 y0 editor|
self closeEditor.
shown ifFalse:[^ self ].
openEditorAction isNil ifTrue:[^ self].
lnNr := self selectedIndex.
lnNr == 0 ifTrue:[^ self].
item := list at:lnNr ifAbsent:nil.
item isNil ifTrue:[^ self].
x < (self xVisibleOfItem:item) ifTrue:[
"/ not part of the selection frame; ignorre
^ self
].
editor := self openEditor.
editor isNil ifTrue:[^ self].
y0 := (y - editor origin y) max:0.
x0 := (x - editor origin x) max:0.
"/ simulate clicking into the editor
self sensor pushEvent:(WindowEvent buttonPress:#select x:x0 y:y0 view:editor).
self sensor pushEvent:(WindowEvent buttonRelease:#select x:x0 y:y0 view:editor).
"/ to clear the selection
self invalidateLineAt:lnNr.
! !
!SelectionInListModelView methodsFor:'protocol'!
drawElementsFrom:start to:stop x:x y:y w:w
"draw the items between start to stop without clearing the background
"
|y0 "{ Class:SmallInteger }"
y1 "{ Class:SmallInteger }"
x0 "{ Class:SmallInteger }"
|
x0 := textStartLeft - viewOrigin x.
y1 := y.
start to:stop do:[:i|
y0 := y1.
y1 := self yVisibleOfLine:(i + 1).
self drawLabelAt:i x:x0 y:y0 h:(y1 - y0)
].
!
lostSynchronisation
"called when the changes derived from the model are faster than the handling
"
self deselectWithoutRedraw.
super lostSynchronisation.
!
xVisibleOfItem:anItem
"returns the visible x of the labeled text
"
^ textStartLeft - viewOrigin x
! !
!SelectionInListModelView methodsFor:'selection'!
deselect
"clear selection
"
self selection:nil
!
firstInSelection
"returns line number of first element selected or nil
"
multipleSelectOk ifFalse:[
^ selection
].
^ selection isNil ifTrue:[nil] ifFalse:[selection at:1]
!
hasSelection
"returns true if a selection exists
"
^ selection notNil
!
isInSelection:aNumber
"return true, if line, aNumber is in the selection
"
multipleSelectOk ifFalse:[
^ aNumber == selection
].
^ selection isNil ifTrue:[false]
ifFalse:[selection includesIdentical:aNumber]
!
isSelectionVisible
"returns true if any selected element is visible"
^ self isSelectionVisibleIn:self extent
!
isSelectionVisibleIn:anExtentPoint
"returns true if any selected element is visible"
self selectionDo:[:aLineNr|
(self isLineVisible:aLineNr in:anExtentPoint) ifTrue:[^ true].
].
^ false
!
numberOfSelections
"return the number of selected items
"
selection notNil ifTrue:[
^ multipleSelectOk ifFalse:[1] ifTrue:[selection size]
].
^ 0
!
selectElement:anElement
"select the element. Scroll to make the new selection visible.
Model and/or actionBlock notification IS done.
"
self selectElement:anElement ifAbsent:[self deselect]
!
selectElement:anElement ifAbsent:exceptionalValue
|idx|
idx := list identityIndexOf:anElement.
idx == 0 ifTrue:[
^ exceptionalValue value
].
self selection:idx
!
selectedElement
"return the single selected item or nil
"
|index|
(index := self selectedIndex) ~~ 0 ifTrue:[
^ list at:index ifAbsent:nil
].
^ nil
!
selectedIndex
"returns the index of the selected line or 0. If more
lines are selected, 0 is returned
"
self numberOfSelections == 1 ifTrue:[
^ self firstInSelection
].
^ 0
!
selection
"return the selection index or collection of indices
in case of multiple selection enabled
"
^ selection
!
selection:something
"select something or deselect if the argument is nil;
scroll to make the selected line visible.
The model and/or actionBlock IS notified.
"
(self selectWithoutScroll:something redraw:true) ifTrue:[
self makeSelectionVisible.
self selectionChanged
].
!
selectionDo:aBlock
"perform aBlock for each nr in the selection.
For single selection, it is called once for the items nr.
For multiple selections, it is called for each.
"
selection notNil ifTrue:[
multipleSelectOk ifTrue:[
selection do:aBlock
] ifFalse:[
aBlock value:selection
].
].
!
setSelection:something
"select something or deselect if the argument is nil;
scroll to make the selected line visible.
*** No model and/or actionBlock notification is done here.
"
(self selectWithoutScroll:something redraw:true) ifTrue:[
self makeSelectionVisible
]
! !
!SelectionInListModelView methodsFor:'selection private'!
canSelectIndex:anIndex
"return true, if the object identified by its index is selectable
"
^ self canSelectIndex:anIndex forAdd:false
!
canSelectIndex:anIndex forAdd:isForAdd
"return true, if the object identified by its index is selectable
"
|item isOk|
selectConditionBlock notNil ifTrue:[
selectConditionBlock numArgs == 2 ifTrue:[
isOk := selectConditionBlock value:anIndex value:isForAdd.
] ifFalse:[
isOk := selectConditionBlock value:anIndex.
].
isOk ifFalse:[
^ false
]
].
item := list at:anIndex ifAbsent:nil.
item isNil ifTrue:[^ false].
item isHierarchicalItem ifTrue:[
^ item isSelectable
].
^ true
!
deselectWithoutRedraw
"clear the selection without redraw and scrolling;
the model and/or actionBlock is notified
"
(self selectWithoutScroll:nil redraw:false) ifTrue:[
self selectionChanged
]
!
makeSelectionVisible
"scroll to make the selection line visible
"
|lineNr|
lineNr := self firstInSelection.
lineNr notNil ifTrue:[
self scrollToLine:lineNr
]
!
selectWithoutScroll:something redraw:doRedraw
"change selection to something; returns true if selection changed
"
|oldSelect|
selection = something ifTrue:[^ false].
oldSelect := selection.
(something isNil or:[something == 0]) ifTrue:[
selection := nil
] ifFalse:[
something isNumber ifTrue:[
selection := multipleSelectOk ifTrue:[Array with:something]
ifFalse:[something]
] ifFalse:[
something size == 0 ifTrue:[
selection := nil
] ifFalse:[
selection := multipleSelectOk ifTrue:[something]
ifFalse:[something at:1]
]
]
].
selection = oldSelect ifTrue:[^ false].
self closeEditor.
(doRedraw and:[shown]) ifFalse:[
^ true
].
multipleSelectOk ifFalse:[
oldSelect notNil ifTrue:[self invalidateSelectionAt:oldSelect].
selection notNil ifTrue:[self invalidateSelectionAt:selection].
] ifTrue:[
(selection notNil and:[oldSelect notNil]) ifTrue:[
selection do:[:i|(oldSelect includesIdentical:i) ifFalse:[self invalidateSelectionAt:i]].
oldSelect do:[:i|(selection includesIdentical:i) ifFalse:[self invalidateSelectionAt:i]].
] ifFalse:[
oldSelect isNil ifTrue:[oldSelect := selection].
oldSelect do:[:i|self invalidateSelectionAt:i]
]
].
^ true
!
toggleSelection:aNumber
"add a number to the selection. No scrolling is done.
*** No model and/or actionBlock notification is done here.
"
|oldSelect doRemove|
multipleSelectOk ifFalse:[
aNumber == selection ifTrue:[ "/ remove from selection
selection := nil.
] ifFalse:[ "/ add to selection
oldSelect := selection.
selection := aNumber.
oldSelect notNil ifTrue:[
self invalidateSelectionAt:oldSelect
]
].
self invalidateSelectionAt:aNumber.
^ self
].
selection isNil ifTrue:[ "/ add to empty selection
selection := OrderedCollection with:aNumber.
self invalidateSelectionAt:aNumber.
^ self
].
doRemove := selection includesIdentical:aNumber.
(doRemove and:[selection size == 1]) ifTrue:[ "/ remove selection
selection := nil.
self invalidateSelectionAt:aNumber.
^ self
].
selection := selection asOrderedCollection.
doRemove ifTrue:[
selection removeIdentical:aNumber.
] ifFalse:[
selection add:aNumber.
].
self invalidateSelectionAt:aNumber.
! !
!SelectionInListModelView class methodsFor:'documentation'!
version
^ '$Header: /cvs/stx/stx/libwidg2/SelectionInListModelView.st,v 1.61 2002-10-24 13:35:19 ca Exp $'
! !