[Pkg] The Trunk: Tools-tpr.772.mcz
commits at source.squeak.org
commits at source.squeak.org
Sat Nov 11 00:32:34 UTC 2017
tim Rowledge uploaded a new version of Tools to project The Trunk:
http://source.squeak.org/trunk/Tools-tpr.772.mcz
==================== Summary ====================
Name: Tools-tpr.772
Author: tpr
Time: 10 November 2017, 4:32:21.463175 pm
UUID: 87c216c9-58b6-4a00-90ce-c7761cd1fa86
Ancestors: Tools-eem.771
Fixes and extensions to FileChooser/Saver Dialogs -
use a mutli-column list for the file list
clean up operation so we don't repeatedly read the directory contents repeatedly again
add hooks for a user message, and a default for each kind of dialog
=============== Diff against Tools-eem.771 ===============
Item was changed:
Model subclass: #FileAbstractSelectionDialog
+ instanceVariableNames: 'pattern directory directoryCache message listIndex fileName finalChoice nameList sizeList dateList'
- instanceVariableNames: 'pattern directory directoryCache list listIndex fileName finalChoice'
classVariableNames: ''
poolDictionaries: ''
category: 'Tools-FileDialogs'!
+ !FileAbstractSelectionDialog commentStamp: 'tpr 11/9/2017 15:37' prior: 0!
- !FileAbstractSelectionDialog commentStamp: 'tpr 10/28/2017 12:55' prior: 0!
FileAbstractSelectionDialog is the abstract superclass for the file chooser & saver modal dialogs.
The UI provides a text input field, a directory tree widget and a list of files within any chosen directory, and buttons to accept the selected file name/path or cancel the operation. See subclass comments and class side methods for specific usage examples.
Instance Variables
directory: <FileDirectory> used for the currently selected directory
directoryCache: <WeakIdentityKeyDictionary> used to cache a boolean to help us more quickly populate the directory tree widget when revisiting a directory
fileName: <String|nil> the name of the currently selected file, if any
finalChoice: <String|nil> pathname of the finally chosen file, returned as the result of accepting; nil is returned otherwise
list: <Array> the list of String of filenames (and date/size) that match the current pattern
listIndex: <Integer> list index of the currently selected file
+ pattern: <String> the pattern is held as a string that may include * or # wildcasrds. See FileAbstractSelectionDialog>>#parsePatternString for details!
- pattern: <String> the pattern is held as a string with three simple tokens;
- a) a ; or LF or CR splits the string into separate patterns and filenames matching any of them will be included in list
- b) a * matches any number of characters
- c) a # matches one character
- The usage of pattern (see entriesMatching:, updateFileList , listForPatterns:) definitely needs improving.!
Item was changed:
----- Method: FileAbstractSelectionDialog>>acceptFileName (in category 'initialize-release') -----
acceptFileName
+ "User clicked to accept the current state so save the filename and close the dialog"
finalChoice := fileName.
self changed: #close!
Item was changed:
----- Method: FileAbstractSelectionDialog>>buildFileListWith: (in category 'toolbuilder') -----
buildFileListWith: builder
| listSpec |
+ listSpec := builder pluggableMultiColumnListSpec new.
- listSpec := builder pluggableListSpec new.
listSpec
model: self ;
list: #fileList ;
getIndex: #fileListIndex ;
setIndex: #fileListIndex: ;
menu: nil ;
keyPress: nil ;
frame:
(self
frameOffsetFromTop:0
fromLeft: 0
width: 1
+ bottomFraction: 1);
+ hScrollBarPolicy: #always .
- bottomFraction: 1) .
^listSpec!
Item was changed:
----- Method: FileAbstractSelectionDialog>>buildWindowWith: (in category 'toolbuilder') -----
buildWindowWith: builder
"Since a file chooser is a modal dialog we over-ride the normal window build to use a dialog as the top component"
| windowSpec |
windowSpec := builder pluggableDialogSpec new.
windowSpec model: self;
label: #windowTitle;
+ message: #userMessage;
extent: self initialExtent;
children: OrderedCollection new;
buttons: OrderedCollection new.
^windowSpec!
Item was changed:
----- Method: FileAbstractSelectionDialog>>buildWith: (in category 'toolbuilder') -----
buildWith: builder
"assemble the spec for the common chooser/saver dialog UI"
+
- "ToolBuilder open: FileChooserDialog"
- "ToolBuilder open: FileSaverDialog"
| windowSpec window |
windowSpec := self buildWindowWith: builder specs: {
(self topConstantHeightFrame: self textViewHeight
fromLeft: 0
width: 1) -> [self buildTextInputWith: builder].
(self frameOffsetFromTop: self textViewHeight
fromLeft: 0.25
width: 0.75
offsetFromBottom: self buttonHeight) -> [self buildFileListWith: builder].
(self frameOffsetFromTop: self textViewHeight
fromLeft: 0
width: 0.25
offsetFromBottom: self buttonHeight) -> [self buildDirectoryTreeWith: builder].
}.
windowSpec buttons add:( builder pluggableButtonSpec new
model: self;
label: 'Accept';
action: #acceptFileName).
windowSpec buttons add:( builder pluggableButtonSpec new
model: self;
label: 'Cancel';
action: #cancelFileChooser).
window := builder build: windowSpec.
self changed: #selectedPath.
^window
!
Item was changed:
----- Method: FileAbstractSelectionDialog>>cancelFileChooser (in category 'initialize-release') -----
cancelFileChooser
+ "User clicked to cancel the current state so nil the filename and close the dialog"
fileName := nil.
self changed: #close.!
Item was added:
+ ----- Method: FileAbstractSelectionDialog>>defaultPattern (in category 'path and pattern') -----
+ defaultPattern
+
+ ^'*'!
Item was changed:
----- Method: FileAbstractSelectionDialog>>directory (in category 'directory tree') -----
directory
+ "If nobody has set a specific directory we need a plausible default"
+ ^ directory ifNil: [ FileDirectory default]!
- ^ directory!
Item was changed:
----- Method: FileAbstractSelectionDialog>>directory: (in category 'directory tree') -----
directory: aFileDirectory
+ "Set the path of the directory to be displayed in the directory tree pane"
+
- "Set the path of the directory to be displayed."
self okToChange ifFalse: [ ^ self ].
self modelSleep.
directory := aFileDirectory.
self modelWakeUp.
+ self changed: #directory!
- self changed: #directory.
- self pattern: pattern!
Item was changed:
----- Method: FileAbstractSelectionDialog>>fileList (in category 'file list') -----
fileList
+ "return the list of files in the currently selected directory; if we haven't yet read an actual directory return empty lists for now"
+
+ nameList ifNil: [nameList := dateList := sizeList := #()].
+ ^Array with: nameList with: dateList with: sizeList!
- "return the list of files in the currently selected directory"
-
- ^list!
Item was changed:
----- Method: FileAbstractSelectionDialog>>fileListIndex (in category 'file list') -----
fileListIndex
+ "return the index in the list of files for the currently selected file; we initialise this to 0 so that the initial listmorph doesn't get upset before we actually populate it with file details - which we don't do until a directory is selected"
- "return the index in the list of files for the currently selected filey"
^listIndex!
Item was removed:
- ----- Method: FileAbstractSelectionDialog>>fileNameFormattedFrom:sizePad: (in category 'path and pattern') -----
- fileNameFormattedFrom: entry sizePad: sizePad
- "entry is a 5-element array of the form:
- (name creationTime modificationTime dirFlag fileSize)"
- | sizeStr nameStr dateStr |
- nameStr := entry isDirectory
- ifTrue: [entry name , self folderString]
- ifFalse: [entry name].
- dateStr := ((Date fromSeconds: entry modificationTime )
- printFormat: #(3 2 1 $. 1 1 2)) , ' ' ,
- (String streamContents: [:s |
- (Time fromSeconds: entry modificationTime \\ 86400)
- print24: true on: s]).
- sizeStr := entry fileSize asStringWithCommas.
- ^ nameStr , ' (' , dateStr , ' ' , sizeStr , ')'
- !
Item was removed:
- ----- Method: FileAbstractSelectionDialog>>fileNameFromFormattedItem: (in category 'file list') -----
- fileNameFromFormattedItem: item
- "Extract fileName and folderString from a formatted fileList item string"
-
- | from to |
- from := item lastIndexOf: $(.
- to := item lastIndexOf: $).
- ^ (from * to = 0
- ifTrue: [item]
- ifFalse: [item copyReplaceFrom: from to: to with: '']) withBlanksTrimmed!
Item was changed:
----- Method: FileAbstractSelectionDialog>>hasMoreDirectories: (in category 'directory tree') -----
hasMoreDirectories: aDirectory
+ "The directory tree morph needs to know if a specific directory has subdirectories; we cache the answer to speed up later visits to the same directory"
+
^directoryCache at: aDirectory ifAbsentPut:[
[aDirectory directoryNames notEmpty] on: Error do:[:ex| true].
].!
Item was changed:
----- Method: FileAbstractSelectionDialog>>initialize (in category 'initialize-release') -----
initialize
super initialize.
directoryCache := WeakIdentityKeyDictionary new.
+ listIndex := 0.
+ pattern := self defaultPattern!
- self directory: FileDirectory default!
Item was changed:
----- Method: FileAbstractSelectionDialog>>listForPatterns: (in category 'path and pattern') -----
+ listForPatterns: arrayOfPatterns
- listForPatterns: anArray
"return a list of those file names which match any of the patterns in the array."
+ | newList |
- | sizePad newList |
newList := Set new.
+ arrayOfPatterns do: [ :pat | newList addAll: (self entriesMatching: pat) ].
- anArray do: [ :pat | newList addAll: (self entriesMatching: pat) ].
- sizePad := (newList inject: 0 into: [:mx :entry | mx max: entry fileSize])
- asStringWithCommas size.
- newList := newList collect: [ :e | self fileNameFormattedFrom: e sizePad: sizePad ].
+ newList := newList sorted: [:a :b|
+ a name <= b name].
+ nameList := newList collect:[:e| e name].
+ dateList := newList collect:[:e| ((Date fromSeconds: e modificationTime )
+ printFormat: #(3 2 1 $. 1 1 2)) , ' ' ,
+ (String streamContents: [:s |
+ (Time fromSeconds: e modificationTime \\ 86400)
+ print24: true on: s])].
+ sizeList := newList collect:[:e| e fileSize asStringWithCommas]
+ !
- ^ newList asArray!
Item was added:
+ ----- Method: FileAbstractSelectionDialog>>parsePatternString (in category 'file list') -----
+ parsePatternString
+ "The pattern is held as a string that may have three simple tokens included along with normal characters;
+ a) a ; or LF or CR splits the string into separate patterns and filenames matching any of them will be included in list
+ b) a * matches any number of characters
+ c) a # matches one character"
+
+ | patterns |
+ patterns := OrderedCollection new.
+ (pattern findTokens: (String with: Character cr with: Character lf with: $;))
+ do: [ :each |
+ (each includes: $*) | (each includes: $#)
+ ifTrue: [ patterns add: each]
+ ifFalse: [each isEmpty
+ ifTrue: [ patterns add: '*']
+ ifFalse: [ patterns add: '*' , each , '*']]].
+
+ ^patterns!
Item was changed:
----- Method: FileAbstractSelectionDialog>>pattern: (in category 'path and pattern') -----
pattern: textOrStringOrNil
+ "Make sure the pattern source string is neither nil nor empty"
textOrStringOrNil
ifNil: [pattern := '*']
ifNotNil: [pattern := textOrStringOrNil asString].
+ pattern isEmpty ifTrue: [pattern := '*']!
- pattern isEmpty ifTrue: [pattern := '*'].
- self updateFileList.
- ^ true
- !
Item was changed:
----- Method: FileAbstractSelectionDialog>>rootDirectoryList (in category 'directory tree') -----
rootDirectoryList
+ "Return a list of know root directories; forms the root nodes ot the directory tree morph"
+
| dirList dir |
dir := FileDirectory on: ''.
dirList := dir directoryNames collect:[:each| dir directoryNamed: each]..
dirList isEmpty ifTrue:[dirList := Array with: FileDirectory default].
^dirList!
Item was changed:
----- Method: FileAbstractSelectionDialog>>selectedPath (in category 'path and pattern') -----
selectedPath
+ "Return an array of directories representing the path from directory up to the root; used to build the directory tree morph"
+
| top here |
top := FileDirectory root.
here := directory.
^(Array streamContents:[:s| | next |
s nextPut: here.
[next := here containingDirectory.
top pathName = next pathName] whileFalse:[
s nextPut: next.
here := next.
]]) reversed.!
Item was changed:
----- Method: FileAbstractSelectionDialog>>setDirectoryTo: (in category 'directory tree') -----
setDirectoryTo: dir
"Set the current directory shown in the FileList.
Does not allow setting the directory to nil since this blows up in various places."
+
dir ifNil:[^self].
self directory: dir.
+ self updateFileList.
+ self changed: #inputText!
- self changed: #fileList.
- self changed: #inputText.!
Item was changed:
----- Method: FileAbstractSelectionDialog>>subDirectoriesOf: (in category 'directory tree') -----
subDirectoriesOf: aDirectory
+
^aDirectory directoryNames collect:[:each| aDirectory directoryNamed: each].!
Item was changed:
----- Method: FileAbstractSelectionDialog>>textViewHeight (in category 'ui details') -----
textViewHeight
" Take a whole font line and 50 % for space "
+
^ (self textViewFont height * 1.5) ceiling!
Item was changed:
----- Method: FileAbstractSelectionDialog>>updateFileList (in category 'file list') -----
updateFileList
+ "Update my files list with file names in the current directory that match the pattern.
+ The pattern string may have embedded newlines or semicolons; these separate multiple different patterns."
- "Update my files list with file names in the current directory
- that match the pattern.
- The pattern string may have embedded newlines or semicolons; these separate different patterns."
- | patterns |
- patterns := OrderedCollection new.
- Cursor wait showWhile: [
- (pattern findTokens: (String with: Character cr with: Character lf with: $;))
- do: [ :each |
- (each includes: $*) | (each includes: $#)
- ifTrue: [ patterns add: each]
- ifFalse: [each isEmpty
- ifTrue: [ patterns add: '*']
- ifFalse: [ patterns add: '*' , each , '*']]].
+ Cursor wait
+ showWhile: [self listForPatterns: self parsePatternString.
+ listIndex := 0.
+ self changed: #fileList]!
- list := self listForPatterns: patterns.
- listIndex := 0.
- fileName := nil.
- self changed: #fileList]!
Item was changed:
----- Method: FileChooserDialog>>fileListIndex: (in category 'file list') -----
fileListIndex: anInteger
+ "We've selected the file at the given index, so find the file name."
- "We've selected the file at the given index,so find the file name."
self okToChange ifFalse: [^ self].
listIndex := anInteger.
listIndex = 0
ifTrue: [fileName := nil]
+ ifFalse: [fileName :=nameList at: anInteger]. "open the file selected"
- ifFalse:
- [fileName := self fileNameFromFormattedItem: (list at: anInteger)]. "open the file selected"
+ self changed: #fileListIndex!
- self
- changed: #fileListIndex!
Item was changed:
----- Method: FileChooserDialog>>inputText (in category 'path and pattern') -----
inputText
"Answers path and pattern together"
+
^directory fullName, directory slash, pattern!
Item was changed:
----- Method: FileChooserDialog>>inputText: (in category 'path and pattern') -----
inputText: stringOrText
+ "both path and pattern are in the text, so split them apart and then change both directory and the match for the filenames before updating the file list"
+
- "both path and pattern are in the text, so split them apart"
| base pat aString |
aString := stringOrText asString.
base := aString copyUpToLast: directory pathNameDelimiter.
pat := aString copyAfterLast: directory pathNameDelimiter.
self changed: #inputText. "avoid asking if it's okToChange"
+
- pattern := pat.
self directory: (FileDirectory on: base).
+ self pattern: pat.
+ self updateFileList.
self changed: #inputText.
self changed: #selectedPath.!
Item was changed:
----- Method: FileChooserDialog>>openOn:pattern: (in category 'initialize-release') -----
openOn: aDirectory pattern: aPatternString
"open a modal dialog to choose a file from aDirectory as filtered by aPattern"
directory := aDirectory.
+ self pattern: aPatternString.
- pattern := aPatternString.
ToolBuilder open: self.
^self finalChoice!
Item was added:
+ ----- Method: FileChooserDialog>>userMessage (in category 'ui details') -----
+ userMessage
+ "return the string to present to the user in order to explain the purpose of this dialog appearing"
+
+ ^message ifNil:['Choose a file name']!
Item was added:
+ ----- Method: FileSaverDialog class>>openOn:initialFilename: (in category 'instance creation') -----
+ openOn: aDirectory initialFilename: aString
+ "open a modal dialog to save a file. Start the dialog with aDirectory selected and the suggested file name"
+
+ ^self new openOn: aDirectory initialFilename: aString
+
+ !
Item was added:
+ ----- Method: FileSaverDialog class>>openOnInitialFilename: (in category 'instance creation') -----
+ openOnInitialFilename: aString
+ "open a modal dialog to save a file. Start the dialog with the default directory selected and the suggested file name"
+
+ ^self new openOn: FileDirectory default initialFilename: aString
+
+ !
Item was added:
+ ----- Method: FileSaverDialog>>acceptFileName (in category 'initialize-release') -----
+ acceptFileName
+ "make sure to accept any edit in the filename before closing"
+
+ self changed: #acceptChanges.
+ ^super acceptFileName!
Item was changed:
----- Method: FileSaverDialog>>fileListIndex: (in category 'file list') -----
fileListIndex: anInteger
+ "We've selected the file at the given index, so find the file name."
- "We've selected the file at the given index,so find the file name."
self okToChange ifFalse: [^ self].
listIndex := anInteger.
listIndex = 0
ifTrue: [fileName := nil]
+ ifFalse: [fileName := nameList at: anInteger]. "open the file selected"
- ifFalse:
- [fileName := self fileNameFromFormattedItem: (list at: anInteger)]. "open the file selected"
self
changed: #fileListIndex;
changed: #inputText!
Item was changed:
----- Method: FileSaverDialog>>inputText: (in category 'filename') -----
inputText: aText
+ "user has entered a potential filename in the text field. If it is a file in the current list, highlight it"
- "user has entered a potential filename in the text field. If it is a file in the currect list, highlight it"
fileName := aText asString.
+ listIndex := nameList findFirst:[: nm| nm = fileName].
- listIndex := list findFirst:[: nm| (self fileNameFromFormattedItem: nm) = fileName].
- listIndex = 0 ifFalse:
- [fileName := self fileNameFromFormattedItem: (list at: listIndex)].
self
changed: #fileListIndex;
+ changed: #inputText!
- changed: #inputFilename!
Item was removed:
- ----- Method: FileSaverDialog>>selectedPath (in category 'path and pattern') -----
- selectedPath
- | top here |
- top := FileDirectory root.
- here := directory.
- ^(Array streamContents:[:s| | next |
- s nextPut: here.
- [next := here containingDirectory.
- top pathName = next pathName] whileFalse:[
- s nextPut: next.
- here := next.
- ]]) reversed.!
Item was added:
+ ----- Method: FileSaverDialog>>userMessage (in category 'ui details') -----
+ userMessage
+ "return the string to present to the user in order to explain the purpose of this dialog appearing"
+
+ ^message ifNil:['Choose a file name; you can also edit the name below to create a new file name']!
More information about the Packages
mailing list