[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