commentary; examples, added ignoreFiles / fileSelectAction & directorySelectAction
"
COPYRIGHT (c) 1993 by Claus Gittinger
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.
"
SelectionInListView subclass:#FileSelectionList
instanceVariableNames:'pattern directory timeStamp directoryId directoryName
directoryContents directoryFileTypes fileTypes realAction
matchBlock stayInDirectory ignoreParentDirectory markDirectories
ignoreDirectories directoryChangeCheckBlock quickDirectoryChange
directoryChangeAction directorySelectAction fileSelectAction
ignoreFiles'
classVariableNames:''
poolDictionaries:''
category:'Views-Text'
!
!FileSelectionList class methodsFor:'documentation'!
copyright
"
COPYRIGHT (c) 1993 by Claus Gittinger
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
"
this class implements file selection lists - its basically a
selection in list, but adds some right-arrows to directories.
(and will soon remember the previous position when changing directories).
You can specify an optional filename-pattern (such as '*.st') and an
optional matchBlock (such as: [:name | name startsWith:'A']).
Only files (plus directories) matching the pattern (if present) and
for which the matchBlock returns true (if present), are shown.
Except for file-browser like applications, FileSelectionLists are almost
exclusively used with FileSelectionBoxes (see examples there).
Instance variables:
pattern the matchpattern
directory the current directory
timeStamp the time, when directoryContents was last taken
directoryId the directories id (inode-nr) when it was taken
directoryName the path when it was taken
directoryContents (cached) contents of current directory
directoryFileTypes (cached) file types (symbols) of current directory
fileTypes file types as shown in list (i.e only matching ones)
matchBlock if non-nil: block evaluated per full filename;
only files for which matchBlock returns true are shown.
realAction (internal) the action to perform when a file is selected
quickDirectoryChange if true, directories can be changed with a single click
if false (the default), they need a double click.
Makes sense if a directory is what we are interrested in,
for files its better to leave it as false.
stayInDirectory if true, no directoryChanges are allowed.
Makes sense to limit the user to choose among certain files.
The default is false.
ignoreParentDirectory if true, the parent directory is not shown.
Makes sense to limit the user to files below the initial
directory. Default is false.
ignoreDirectories if true, no directories are shown at all.
Makes sense to limit the user to choose among regular files.
Default is false.
ignoreFiles if true, no regular files are shown at all.
Makes sense to limit the user to choose among directories files.
Default is false.
directoryChangeCheckBlock
if nonNil, directoryChanges are only allowed if this block
returns true. It is evaluated with one argument, the pathName.
Defaults to nil (i.e. no checks).
directorySelectAction
if nonNil, a directory-select evaluate this block.
Possible hook for others (used with Boxes)
Defaults to nil.
fileSelectAction
if nonNil, file-select evaluate this block.
Possible hook for others (used with Boxes)
Defaults to nil.
"
!
examples
"
FileSelectionLists are typically used in FileSelectionBoxes,
or file-browser-like applications.
Thus, the following examples are a bit untypical.
example (plain file-list):
|list|
list := FileSelectionList new.
list open
scrolled & some action:
|top v list|
top := StandardSystemView new.
top extent:(300 @ 200).
v := ScrollableView for:FileSelectionList in:top.
v origin:(0.0 @ 0.0) corner:(1.0 @ 1.0).
list := v scrolledView.
list action:[:index | Transcript showCr:'you selected: ' , list selectionValue].
top open
ignore the parentDirectory:
|top v list|
top := StandardSystemView new.
top extent:(300 @ 200).
v := ScrollableView for:FileSelectionList in:top.
v origin:(0.0 @ 0.0) corner:(1.0 @ 1.0).
list := v scrolledView.
list ignoreParentDirectory:true.
top open
ignore all directories (i.e. regular files only):
|top v list|
top := StandardSystemView new.
top extent:(300 @ 200).
v := ScrollableView for:FileSelectionList in:top.
v origin:(0.0 @ 0.0) corner:(1.0 @ 1.0).
list := v scrolledView.
list ignoreDirectories:true.
top open
ignore all regular files (i.e. directories only):
|top v list|
top := StandardSystemView new.
top extent:(300 @ 200).
v := ScrollableView for:FileSelectionList in:top.
v origin:(0.0 @ 0.0) corner:(1.0 @ 1.0).
list := v scrolledView.
list ignoreFiles:true.
top open
dont show the directory arrow-mark:
|top v list|
top := StandardSystemView new.
top extent:(300 @ 200).
v := ScrollableView for:FileSelectionList in:top.
v origin:(0.0 @ 0.0) corner:(1.0 @ 1.0).
list := v scrolledView.
list markDirectories:false.
top open
adds a pattern, only showing .st files and directories:
|top v list|
top := StandardSystemView new.
top extent:(300 @ 200).
v := ScrollableView for:FileSelectionList in:top.
v origin:(0.0 @ 0.0) corner:(1.0 @ 1.0).
list := v scrolledView.
list pattern:'*.st'.
list action:[:index | Transcript showCr:'you selected: ' , list selectionValue].
top open
a more complicated pattern:
|top v list|
top := StandardSystemView new.
top extent:(300 @ 200).
v := ScrollableView for:FileSelectionList in:top.
v origin:(0.0 @ 0.0) corner:(1.0 @ 1.0).
list := v scrolledView.
list pattern:'[A-D]*.st'.
list action:[:index | Transcript showCr:'you selected: ' , list selectionValue].
top open
adds a matchblock to show only writable files:
|top v list|
top := StandardSystemView new.
top extent:(300 @ 200).
v := ScrollableView for:FileSelectionList in:top.
v origin:(0.0 @ 0.0) corner:(1.0 @ 1.0).
list := v scrolledView.
list matchBlock:[:name |
|fileName|
fileName := name asFilename.
fileName isWritable or:[fileName isDirectory]
].
list action:[:index | Transcript showCr:'you selected: ' , list selectionValue].
top open
adds a matchblock to suppress directories:
(this can be done easier with #ignoreDirectories)
|top v list|
top := StandardSystemView new.
top extent:(300 @ 200).
v := ScrollableView for:FileSelectionList in:top.
v origin:(0.0 @ 0.0) corner:(1.0 @ 1.0).
list := v scrolledView.
list matchBlock:[:name |
name asFilename isDirectory not
].
list action:[:index | Transcript showCr:'you selected: ' , list selectionValue].
top open
the above can be done more convenient:
|top v list|
top := StandardSystemView new.
top extent:(300 @ 200).
v := ScrollableView for:FileSelectionList in:top.
v origin:(0.0 @ 0.0) corner:(1.0 @ 1.0).
list := v scrolledView.
list ignoreDirectories:true.
list action:[:index | Transcript showCr:'you selected: ' , list selectionValue].
top open
adds a matchblock to block parent dirs (i.e. only allow files here & below):
(can be done easier with #ignoreParentDirectory)
|top v list currentDir|
currentDir := '.' asFilename pathName.
top := StandardSystemView new.
top extent:(300 @ 200).
v := ScrollableView for:FileSelectionList in:top.
v origin:(0.0 @ 0.0) corner:(1.0 @ 1.0).
list := v scrolledView.
list matchBlock:[:name |
((name endsWith:'/..') and:[list directory pathName = currentDir]) not
].
list action:[:index | Transcript showCr:'you selected: ' , list selectionValue].
top open
do not allow changing up AND show all .rc-files only:
(but allow going down)
|top v list currentDir|
currentDir := '.' asFilename pathName.
top := StandardSystemView new.
top extent:(300 @ 200).
v := ScrollableView for:FileSelectionList in:top.
v origin:(0.0 @ 0.0) corner:(1.0 @ 1.0).
list := v scrolledView.
list pattern:'*.rc'.
list matchBlock:[:name |
((name endsWith:'/..') and:[list directory pathName = currentDir]) not
].
list action:[:index | Transcript showCr:'you selected: ' , list selectionValue].
top open
show only .rc-files in current directory:
|top v list currentDir|
currentDir := '.' asFilename pathName.
top := StandardSystemView new.
top extent:(300 @ 200).
v := ScrollableView for:FileSelectionList in:top.
v origin:(0.0 @ 0.0) corner:(1.0 @ 1.0).
list := v scrolledView.
list pattern:'*.rc'.
list matchBlock:[:name |
name asFilename isDirectory not
].
list action:[:index | Transcript showCr:'you selected: ' , list selectionValue].
top open
show only h*-files in /etc; dont allow directory changes:
|top v list|
top := StandardSystemView new.
top extent:(300 @ 200).
v := ScrollableView for:FileSelectionList in:top.
v origin:(0.0 @ 0.0) corner:(1.0 @ 1.0).
list := v scrolledView.
list directory:'/etc'.
list pattern:'h*'.
list matchBlock:[:name | name printNL.
name asFilename isDirectory not
].
list action:[:index | Transcript showCr:'you selected: ' , list selectionValue].
top open
only allow changing into directories below the current one; i.e. not up;
but show it
|top v list here|
top := StandardSystemView new.
top extent:(300 @ 200).
v := ScrollableView for:FileSelectionList in:top.
v origin:(0.0 @ 0.0) corner:(1.0 @ 1.0).
list := v scrolledView.
list directoryChangeCheckBlock:[:dirPath |
dirPath asFilename pathName
startsWith:Filename currentDirectory pathName].
list action:[:index | Transcript showCr:'you selected: ' , list selectionValue].
top open
living in a box:
|box listView|
box := Dialog new.
box addTextLabel:'which file ?'.
listView := box
addListBoxOn:nil
class:FileSelectionList
withNumberOfLines:10
hScrollable:false
vScrollable:true.
box addAbortButton; addOkButton.
box open.
box accepted ifTrue:[
Transcript showCr:listView selectedPathname
]
living in a box (local files only, no directory change allowed):
|box listView|
box := Dialog new.
box addTextLabel:'which file ?'.
listView := box
addListBoxOn:nil
class:FileSelectionList
withNumberOfLines:10
hScrollable:false
vScrollable:true.
listView ignoreDirectories:true.
listView ignoreParentDirectory:true.
box addAbortButton; addOkButton.
box open.
box accepted ifTrue:[
Transcript showCr:listView selectedPathname
]
living in a box (local files only; immediately show owner in another field):
|box listView lbl|
box := Dialog new.
box addTextLabel:'which file ?'.
listView := box
addListBoxOn:nil
class:FileSelectionList
withNumberOfLines:10
hScrollable:false
vScrollable:true.
lbl := box addTextLabel:''.
lbl adjust:#left.
listView fileSelectAction:[:index |
|ownerId owner|
ownerId := listView selectedPathname asFilename info at:#uid.
ownerId == OperatingSystem getUserID ifTrue:[
lbl label:('one of yours').
] ifFalse:[
owner := OperatingSystem getUserNameFromID:ownerId.
lbl label:(owner , '''s property').
]
].
listView directorySelectAction:[:index |
|ownerId owner|
ownerId := listView selectedPathname asFilename info at:#uid.
ownerId == OperatingSystem getUserID ifTrue:[
lbl label:('your files there').
] ifFalse:[
owner := OperatingSystem getUserNameFromID:ownerId.
lbl label:(owner , '''s files there').
]
].
box addAbortButton; addOkButton.
box open.
box accepted ifTrue:[
Transcript showCr:listView selectedPathname
]
"
! !
!FileSelectionList methodsFor:'accessing-behavior'!
action:aBlock
"set the action to be performed on a selection"
realAction := aBlock
!
directoryChangeAction:aBlock
"set the action to be performed on a directory change"
directoryChangeAction := aBlock
"Created: 5.3.1996 / 02:37:08 / cg"
!
directoryChangeCheckBlock:aBlock
"set the directoryChangeCheckBlock - if non-nil, it controls if
a directory change is legal."
directoryChangeCheckBlock := aBlock
!
directorySelectAction:aBlock
"set the action to be performed when a directory is selected.
Useful if someone else wants to show additional information
(readable/owner ...) somewhere."
directorySelectAction := aBlock
"Created: 18.4.1996 / 18:45:13 / cg"
!
fileSelectAction:aBlock
"set the action to be performed when a file is selected.
Useful if someone else wants to show additional information
(readable/owner ...) somewhere."
fileSelectAction := aBlock
"Created: 18.4.1996 / 18:45:24 / cg"
!
ignoreDirectories:aBoolean
"set/clear the flag which controls if directories are ignored
(i.e. hidden). The default is false (i.e. dirs are shown)"
ignoreDirectories := aBoolean
!
ignoreFiles:aBoolean
"set/clear the flag which controls if plain files are ignored
(i.e. hidden). The default is false (i.e. they are shown)"
ignoreFiles := aBoolean
"Created: 18.4.1996 / 18:48:43 / cg"
"Modified: 18.4.1996 / 18:49:23 / cg"
!
ignoreParentDirectory:aBoolean
"set/clear the flag which controls if the parent directory (..)
is shown in the list. The default is false (i.e. show it)"
ignoreParentDirectory := aBoolean
!
markDirectories:aBoolean
"turn on/off marking of directories with an arrow.
The default is on"
markDirectories := aBoolean
!
matchBlock:aBlock
"set the matchBlock - if non-nil, it controls which
names are shown in the list."
matchBlock := aBlock
!
pattern:aPattern
"set the pattern - if it changes, update the list."
pattern ~= aPattern ifTrue:[
pattern := aPattern.
realized ifTrue:[
self updateList
].
].
!
quickDirectoryChange:aBoolean
"set/clear quick change (i.e. chdir with single click).
The default is false (i.e. double click is required)"
quickDirectoryChange := aBoolean
"Created: 4.3.1996 / 17:37:58 / cg"
!
stayInDirectory:aBoolean
"set/clear the flag which controls if selecting a directory
should locally change (if false) or be handled just like
the selection of a file (if true).
The default is false (i.e. change and do not tell via action)"
stayInDirectory := aBoolean
! !
!FileSelectionList methodsFor:'accessing-contents'!
directory
"return the shown directory"
^ directory
!
directory:nameOrDirectory
"set the lists contents to the filenames in the directory.
This does not validate the change with any directoryChangeBlock."
|oldPath name|
nameOrDirectory isString ifTrue:[
name := nameOrDirectory
] ifFalse:[
nameOrDirectory isNil ifTrue:[
directory := nil.
^ self updateList
].
name := nameOrDirectory pathName
].
directory isNil ifTrue:[
directory := FileDirectory new.
oldPath := nil
] ifFalse:[
oldPath := directory pathName.
].
directory pathName:name.
realized ifTrue:[
(directory pathName = oldPath) ifFalse:[
self updateList
]
]
!
selectedPathname
"if there is a selection, return its full pathname.
Of there is no selection, return nil."
|sel|
sel := self selectionValue.
sel isNil ifTrue:[^ nil].
^ directory pathName , Filename separator asString , sel.
! !
!FileSelectionList methodsFor:'drawing'!
redrawFromVisibleLine:startVisLineNr to:endVisLineNr
"redefined to look for directory in every line"
|l|
"first, draw chunk of lines"
super redrawFromVisibleLine:startVisLineNr to:endVisLineNr.
markDirectories ifFalse:[^ self].
"then draw marks"
startVisLineNr to:endVisLineNr do:[:visLineNr |
l := self visibleLineToListLine:visLineNr.
l notNil ifTrue:[
(fileTypes at:l) == #directory ifTrue:[
self drawRightArrowInVisibleLine:visLineNr
]
]
]
!
redrawVisibleLine:visLineNr
"if the line is one for a directory, draw a right arrow"
|l|
super redrawVisibleLine:visLineNr.
markDirectories ifFalse:[^ self].
l := self visibleLineToListLine:visLineNr.
l notNil ifTrue:[
(fileTypes at:l) == #directory ifTrue:[
self drawRightArrowInVisibleLine:visLineNr
]
]
! !
!FileSelectionList methodsFor:'events'!
doubleClicked
self selectionIsDirectory ifTrue:[
stayInDirectory not ifTrue:[
quickDirectoryChange ifFalse:[
directoryChangeAction notNil ifTrue:[
directoryChangeAction value:self selection
] ifFalse:[
self changeDirectory
]
]
].
^ self
].
super doubleClicked
"Created: 4.3.1996 / 17:39:58 / cg"
"Modified: 5.3.1996 / 02:38:06 / cg"
!
selectionChanged
"if the selection changed, check for it being a directory
and possibly go there. If its not a directory, perform the realAction."
self selection isCollection ifFalse:[
self selectionIsDirectory ifTrue:[
(stayInDirectory not and:[quickDirectoryChange]) ifTrue:[
directoryChangeAction notNil ifTrue:[
directoryChangeAction value:self selection
] ifFalse:[
self changeDirectory
]
] ifFalse:[
directorySelectAction notNil ifTrue:[
directorySelectAction value:self selection
]
]
] ifFalse:[
realAction notNil ifTrue:[
realAction value:self selection
].
fileSelectAction notNil ifTrue:[
fileSelectAction value:self selection
]
]
]
"Modified: 18.4.1996 / 18:44:30 / cg"
!
sizeChanged:how
"redraw marks if any"
super sizeChanged:how.
(shown and:[markDirectories]) ifTrue:[
self redraw
]
! !
!FileSelectionList methodsFor:'initialization'!
initialize
directory := FileDirectory currentDirectory.
stayInDirectory := ignoreParentDirectory := ignoreDirectories := false.
ignoreFiles := quickDirectoryChange := false.
markDirectories := true.
super initialize.
pattern := '*'.
self initializeAction.
"nontypical use ..."
"
FileSelectionList new open
(FileSelectionList new directory:'/etc') open
(ScrollableView for:FileSelectionList) open
(HVScrollableView for:FileSelectionList) open
"
"Modified: 18.4.1996 / 18:49:19 / cg"
!
initializeAction
"setup action as: selections in list get forwarded to enterfield if not
a directory; otherwise directory is changed"
actionBlock := [:lineNr | self selectionChanged].
"/ doubleClickActionBlock := [:lineNr | self selectionChanged].
"Modified: 4.3.1996 / 17:39:08 / cg"
!
reinitialize
directory := FileDirectory currentDirectory.
super reinitialize
! !
!FileSelectionList methodsFor:'private'!
changeDirectory
"change directory to the selected one"
|entry ok newDir warnMessage oldDir|
entry := self selectionValue.
(entry isNil or:[entry isEmpty]) ifTrue:[ ^ false].
(entry endsWith:' ...') ifTrue:[
entry := entry copyWithoutLast:4.
].
ok := false.
oldDir := directory pathName asFilename baseName.
newDir := directory pathName , Filename separator asString , entry.
(directoryChangeCheckBlock isNil
or:[directoryChangeCheckBlock value:newDir]) ifTrue:[
(directory isReadable:entry) ifFalse:[
warnMessage := 'not allowed to read directory %1'
] ifTrue:[
(directory isExecutable:entry) ifFalse:[
warnMessage := 'not allowed to change to directory %1'
] ifTrue:[
ok := true.
]
].
].
ok ifFalse:[
warnMessage notNil ifTrue:[
self warn:(resources string:warnMessage with:entry).
].
self deselect
] ifTrue:[
self directory:newDir.
entry = '..' ifTrue:[
self selectElement:oldDir
].
].
"Created: 4.3.1996 / 17:45:18 / cg"
"Modified: 4.3.1996 / 18:10:17 / cg"
!
selectionIsDirectory
|entry|
entry := self selectionValue.
(entry isNil or:[entry isEmpty]) ifTrue:[ ^ false].
(entry endsWith:' ...') ifTrue:[
entry := entry copyWithoutLast:4.
].
^ (directory typeOf:entry) == #directory
"Created: 4.3.1996 / 17:43:26 / cg"
!
updateList
"set the lists contents to the filenames in the directory"
|oldCursor files newList index path obsolete matching patternList|
directory isNil ifTrue:[
super list:nil.
files := newList := fileTypes := nil.
^ self
].
oldCursor := cursor.
self cursor:(Cursor read).
"
if the directory-id changed, MUST update.
(can happen after a restart, when a file is no longer
there, has moved or is NFS-mounted differently)
"
obsolete := directoryId ~~ directory id
or:[directoryName ~= directory pathName
or:[timeStamp notNil
and:[directory timeOfLastChange > timeStamp]]].
obsolete ifTrue:[
timeStamp := directory timeOfLastChange.
directoryId := directory id.
directoryName := directory pathName.
directoryContents := directory asStringCollection sort.
directoryFileTypes := OrderedCollection new.
directoryContents do:[:name | directoryFileTypes add:(directory typeOf:name)].
].
files := directoryContents.
newList := OrderedCollection new.
fileTypes := OrderedCollection new.
index := 1.
path := directory pathName , Filename separator asString.
files do:[:name |
|type|
(matchBlock isNil or:[matchBlock value:(path , name)]) ifTrue:[
type := directoryFileTypes at:index.
type == #directory ifTrue:[
ignoreDirectories ifFalse:[
name = '..' ifTrue:[
ignoreParentDirectory ifFalse:[
newList add:name.
fileTypes add:type
]
] ifFalse:[
name = '.' ifTrue:[
"ignore"
] ifFalse:[
newList add:(name ", ' ...'").
fileTypes add:type
]
]
]
] ifFalse:[
ignoreFiles ifFalse:[
matching := true.
(pattern isNil
or:[pattern isEmpty]) ifFalse:[
pattern = '*' ifFalse:[
(pattern includes:$;) ifTrue:[
patternList := pattern asCollectionOfSubstringsSeparatedBy:$;.
matching := (patternList findFirst:[:subPattern | subPattern match:name]) ~~ 0.
] ifFalse:[
matching := pattern match:name
]
]
].
matching ifTrue:[
newList add:name.
fileTypes add:type
]
]
].
].
index := index + 1
].
super list:newList.
self cursor:oldCursor.
"Modified: 18.4.1996 / 18:49:48 / cg"
!
visibleLineNeedsSpecialCare:visLineNr
|l|
l := self visibleLineToListLine:visLineNr.
l notNil ifTrue:[
(fileTypes at:l) == #directory ifTrue:[^ true].
^ super visibleLineNeedsSpecialCare:visLineNr
].
^ false
!
widthForScrollBetween:firstLine and:lastLine
"return the width in pixels for a scroll between firstLine and lastLine
- return full width here since there might be directory marks"
^ (width - margin - margin)
! !
!FileSelectionList methodsFor:'realization'!
realize
"make the view visible; redefined to check if directory is still
valid (using timestamp and inode numbers) - reread if not"
(timeStamp isNil
or:[(directory timeOfLastChange > timeStamp)
or:[(directoryId isNil)
or:[directoryId ~~ directory id]]]) ifTrue:[
directoryId := nil.
self updateList
].
super realize
! !
!FileSelectionList class methodsFor:'documentation'!
version
^ '$Header: /cvs/stx/stx/libwidg/FileSelectionList.st,v 1.27 1996-04-19 11:26:09 cg Exp $'
! !