The Trunk: Tools-tpr.772.mcz

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
3 messages Options
Reply | Threaded
Open this post in threaded view
|

The Trunk: Tools-tpr.772.mcz

commits-2
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']!


Reply | Threaded
Open this post in threaded view
|

Re: The Trunk: Tools-tpr.772.mcz

Chris Muller-3
I haven't tried this yet, but I do hope you haven't broken the
list-filtering function or other keyboard shortcuts.  For me, a
multicolumn list is much less important to usability than the ability
to interact with the file list via the keyboard.

On Fri, Nov 10, 2017 at 6:32 PM,  <[hidden email]> wrote:

> 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']!
>
>

Reply | Threaded
Open this post in threaded view
|

Re: The Trunk: Tools-tpr.772.mcz

timrowledge

> On 10-11-2017, at 5:36 PM, Chris Muller <[hidden email]> wrote:
>
> I haven't tried this yet, but I do hope you haven't broken the
> list-filtering function or other keyboard shortcuts.  For me, a
> multicolumn list is much less important to usability than the ability
> to interact with the file list via the keyboard.

Note that this is *new* stuff, not changes to stuff that has been around before. So at the worst all we have to do is delete the classes.

Given that the dialogs are built with the ToolBuilder I’d hope that it has all the functions that you’d expect built in - other wise we’ve messed up and not produced a very useful tool building tool. So far as I can see the tree view seems to do what I’d expect with cursor keys etc. It doesn’t look like the file list does anything other than up/down cursor actions. I did point out that PluggableMultiColumnList appeared to need some love; I can’t imagine it would be too complex to move over appropriate functionality so long as one knows what that is! The FileChooserDialog of course has filtering via the pattern input field anyway.

There are no menus to worry about right now and I can’t think of any that would be needed.. I think? Maybe add a connection to a ServerDirectory sometime, and that could need a menu or extra button? There aren’t replacements of FileList as a file browser, but of the rather messy (ab)use of bits of FileList (and FileList2 in some places) as UIs to pick a file to load or a place to save a file.


tim
--
tim Rowledge; [hidden email]; http://www.rowledge.org/tim
Any program that runs right is obsolete.