[squeak-dev] Review Request: polish-file-dialogs.3.cs

christoph.thiede at student.hpi.uni-potsdam.de christoph.thiede at student.hpi.uni-potsdam.de
Sat Jul 9 17:32:46 UTC 2022


Hi Marcel,

thanks for the feedback! Revised again and merged. :-)

> Browser>>updateCodePaneIfNeeded
> CodeHolder>>updateCodePaneIfNeeded
> 
> Why is that in there?

Just a changeset slip, which I have resolved now.

Best,
Christoph

---
Sent from Squeak Inbox Talk

On 2022-07-07T10:57:30+02:00, marcel.taeumel at hpi.de wrote:

> Hi Christoph --
> 
> Thanks! Looks good except maybe for:
> 
> Browser>>updateCodePaneIfNeeded
> CodeHolder>>updateCodePaneIfNeeded
> 
> Why is that in there?
> 
> Maybe give your changes a fresh look with our own eyes again. And then improve the code with what you learned during the last 5 weeks. :-D
> 
> Best,
> Marcel
> Am 20.05.2022 16:31:16 schrieb christoph.thiede at student.hpi.uni-potsdam.de <christoph.thiede at student.hpi.uni-potsdam.de>:
> Feel free to try this out already and give feedback, but given that this includes new features, it will not be merged before the next release. :-)
> 
> Best,
> Christoph
> 
> =============== Summary ===============
> 
> Change Set:        polish-file-dialogs
> Date:            20 May 2022
> Author:            Christoph Thiede
> 
> Cleans up, tests, and improves the convenience of file selection dialogs.
> 
> UI improvements:
> * Add input field for file path to all dialogs. In file dialogs, a directory path can be entered to navigate to the relevant directory in the tree.
> * Update enablement of canAccept button based on input
> * Improve automatic selection of filenames
> * Use explicit help texts instead of filling the input field with a help message
> * Fixes handling of patterns/suffixes in save dialog
> * Double click a file/directory to choose it
> * Save dialog: Assure that the name of an existing directory cannot be chosen as a new file name
> * Small MVC improvements (however, modal invocation in MVC is still broken at the moment)
> 
> Refactoring:
> * Overall deduplication
> * Consistent spelling of fileName (instead of filename)
> 
> =============== Diff ===============
> 
> Browser>>updateCodePaneIfNeeded {self-updating} · ct 5/20/2022 13:00
> + updateCodePaneIfNeeded
> +
> +     super updateCodePaneIfNeeded.
> +     
> +     (self didCodeChangeElsewhere and: [self hasUnacceptedEdits not])
> +         ifTrue:
> +             [self setClassDefinition.
> +             self contentsChanged].
> 
> CodeHolder>>updateCodePaneIfNeeded {self-updating} · sw 2/14/2001 15:34 (changed)
> updateCodePaneIfNeeded
>     "If the code for the currently selected method has changed underneath me, then update the contents of my code pane unless it holds unaccepted edits"
> 
>     self didCodeChangeElsewhere
>         ifTrue:
>             [self hasUnacceptedEdits
>                 ifFalse:
>                     [self setContentsToForceRefetch.
>                     self contentsChanged]
>                 ifTrue:
>                     [self changed: #codeChangedElsewhere]]
> 
> DirectoryChooserDialog (source same but rev changed)
> FileAbstractSelectionDialog subclass: #DirectoryChooserDialog
>     instanceVariableNames: ''
>     classVariableNames: ''
>     poolDictionaries: ''
>     category: 'ToolBuilder-Morphic-Tools'
> 
> DirectoryChooserDialog class
>     instanceVariableNames: ''
> 
> "A DirectoryChooserDialog is a modal dialog to allow choosing a directory. The actual directory chosen is returned, or nil if no selection was made.
> 
> Normal usage would be
>     myDirectory := DirectoryChooserDialog openOn: myApplicationDefaultDirectory label: 'Choose the directory to use'
> "
> 
> DirectoryChooserDialog>>acceptDirectory: {directory tree} · ct 5/20/2022 12:11
> + acceptDirectory: dir
> +
> +     self setDirectoryTo: dir.
> +     self acceptFileName.
> 
> DirectoryChooserDialog>>acceptFileName {accessing} · ct 5/20/2022 11:47 (changed and recategorized)
> acceptFileName
>     "User clicked to accept the current state so save the directory and close the dialog"
> 
> +     self canAccept ifFalse: [^ false].
>     finalChoice := directory.
> -     self changed: #close
> +     self changed: #close.
> +     ^ true
> 
> DirectoryChooserDialog>>buildDirectoryTreeWith: {toolbuilder} · ct 5/20/2022 12:10 (changed)
> buildDirectoryTreeWith: builder
> 
>     ^ (super buildDirectoryTreeWith: builder)
>         hScrollBarPolicy: #never; "Use the dialog grips to see more"
> +         doubleClick: #acceptDirectory:;
>         yourself
> 
> DirectoryChooserDialog>>buildWith: {toolbuilder} · ct 5/19/2022 18:10 (changed)
> buildWith: builder
>     "assemble the spec for the chooser dialog UI"
> 
>     | windowSpec window |
>     windowSpec := self buildWindowWith: builder specs: {
> -         (self frameOffsetFromTop: 0
> +         (self topConstantHeightFrame: self textViewHeight
>             fromLeft: 0
> +             width: 1) -> [self buildTextInputWith: builder].
> +         (self frameOffsetFromTop: self textViewHeight
> +             fromLeft: 0
>             width: 1
>             offsetFromBottom: 0) -> [self buildDirectoryTreeWith: builder].
>     }.
>     windowSpec buttons addAll: ( self buildButtonsWith: builder ).
>     window := builder build: windowSpec.
> -     window addKeyboardCaptureFilter: self.
> +     (window respondsTo: #addKeyboardCaptureFilter: ) ifTrue: [
> +         window addKeyboardCaptureFilter: self].
>     self changed: #selectedPath.
>     ^window
> 
> 
> DirectoryChooserDialog>>canAccept {accessing} · ct 5/19/2022 18:03
> + canAccept
> +
> +     ^ directory notNil and: [directory exists]
> 
> DirectoryChooserDialog>>inputText {filename} · ct 5/19/2022 17:40
> + inputText
> +
> +     ^ directory fullName
> 
> DirectoryChooserDialog>>inputText: {filename} · ct 5/20/2022 12:52
> + inputText: aText
> +
> +     ^ self selectFileName: aText
> 
> DirectoryChooserDialog>>selectFileName: {filename} · ct 5/20/2022 13:19
> + selectFileName: aStringOrText
> +
> +     aStringOrText ifNil: [^ self].
> +     self directory: ([FileDirectory on: aStringOrText asString] ifError: [^ self]).
> +     isUpdating := true.
> +     [self changed: #selectedPath]
> +         ensure: [isUpdating := false].
> +     self updateFileList.
> +     self changed: #canAccept.
> 
> DirectoryChooserDialogTest
> + nil subclass: #DirectoryChooserDialogTest
> +     instanceVariableNames: ''
> +     classVariableNames: ''
> +     poolDictionaries: ''
> +     category: 'MorphicTests-ToolBuilder'
> +
> + DirectoryChooserDialogTest class
> +     instanceVariableNames: ''
> +
> + ""
> 
> DirectoryChooserDialogTest>>expectedFailures {failures} · ct 5/19/2022 22:16
> + expectedFailures
> +
> +     self flag: #todo. "Can only be debugged, but not run - this raises an InvalidDirectoryError which's defaultAction handles the exception silently. Should this class be a Notification instead?"
> +     ^ #(testChooseAbsentDirectory testTypeAndChooseAbsentDirectory)
> 
> DirectoryChooserDialogTest>>testChooseAbsentDirectory {tests - interface} · ct 5/20/2022 12:53
> + testChooseAbsentDirectory
> +
> +     self openDialog.
> +     
> +     dialog acceptFileName: (self pathForFile: 'nurp').
> +     
> +     self assert: nil equals: result.
> 
> DirectoryChooserDialogTest>>testChooseDefault {tests - interactions} · ct 5/19/2022 21:52
> + testChooseDefault
> +
> +     self openDialog.
> +     
> +     self assert: dialog canAccept.
> +     dialog acceptFileName.
> +     
> +     self assert: mockDirectory equals: result.
> 
> DirectoryChooserDialogTest>>testChooseDirectory {tests - interface} · ct 5/20/2022 12:53
> + testChooseDirectory
> +
> +     self openDialog.
> +     
> +     dialog acceptFileName: mockChildDirectory fullName.
> +     
> +     self assert: mockChildDirectory equals: result.
> 
> DirectoryChooserDialogTest>>testNewDirectory {tests - interface} · ct 5/19/2022 23:57
> + testNewDirectory
> +
> +     self openDialog.
> +     
> +     [dialog newDirectoryName] valueSupplyingAnswer: #('*name*' 'nurp').
> +     self assert: (mockDirectory directoryExists: 'nurp').
> +     self assert: mockDirectory / 'nurp' equals: dialog directory.
> +     dialog acceptFileName.
> +     
> +     self assert: mockDirectory / 'nurp' equals: result.
> 
> DirectoryChooserDialogTest>>testSelectAndChooseDirectory {tests - interactions} · ct 5/19/2022 21:54
> + testSelectAndChooseDirectory
> +
> +     self openDialog.
> +     
> +     dialog setDirectoryTo: (dialog subDirectoriesOf: mockDirectory) first.
> +     self assert: dialog canAccept.
> +     dialog acceptFileName.
> +     
> +     self assert: mockChildDirectory equals: result.
> 
> DirectoryChooserDialogTest>>testTypeAndChooseAbsentDirectory {tests - interactions} · ct 5/20/2022 12:52
> + testTypeAndChooseAbsentDirectory
> +
> +     self openDialog.
> +     
> +     dialog selectFileName: (self pathForDirectory: mockDirectory file: 'twin').
> +     self deny: dialog canAccept.
> +     dialog acceptFileName.
> +     
> +     self assert: nil equals: result.
> 
> DirectoryChooserDialogTest>>testTypeAndChooseDirectory {tests - interactions} · ct 5/20/2022 12:53
> + testTypeAndChooseDirectory
> +
> +     self openDialog.
> +     
> +     dialog selectFileName: mockChildDirectory fullName.
> +     self assert: dialog canAccept.
> +     dialog acceptFileName.
> +     
> +     self assert: mockChildDirectory equals: result.
> 
> FileAbstractSelectionDialog (changed)
> Model subclass: #FileAbstractSelectionDialog
> -     instanceVariableNames: 'patternList directory directoryCache message listIndex fileName finalChoice nameList sizeList dateList suffixList'
> +     instanceVariableNames: 'patternList directory directoryCache message listIndex fileName finalChoice nameList sizeList dateList suffixList isUpdating'
>     classVariableNames: ''
>     poolDictionaries: ''
>     category: 'ToolBuilder-Morphic-Tools'
> 
> FileAbstractSelectionDialog class
>     instanceVariableNames: ''
> 
> "FileAbstractSelectionDialog is the abstract superclass for the file chooser & saver modal dialogs.
> 
> The UI provides a message to the user, 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
>     patternList:        <OrderedCollection of String> the patterns are held as a collection of string that may include * or # wildcards. See FileAbstractSelectionDialog>>#parsePatternString for details
>     message:        <String> a message to the user to explain what is expected
>     nameList,DateList, sizeList:    <Array> the list of file names matching the pattern and the appropriate date and size values, formatted for a PluggableMultiColumnListMorph"
> 
> FileAbstractSelectionDialog class>>todo {documentation} · ct 5/20/2022 12:46
> + todo
> +
> +     self flag: #forLater. "Possible future adventures for the file dialogs:
> +     
> +         * Allow users to enter patterns with stars to filter the current fileList (as known from Microsoft Windows file dialogs)
> +         * Add support for multiple file selection
> +         * Add MVC support (currently, modal dialog invocations windows seems not to work there). See also existing #mvc flag.
> +         * Normalize paths with parentDirectoryNickname"
> 
> FileAbstractSelectionDialog>>acceptFileName {accessing} · ct 5/20/2022 12:32 (changed and recategorized)
> acceptFileName
> -     "User clicked to accept the current state so save the filename and close the dialog"
> 
> -     finalChoice := fileName.
> -     self changed: #close
> +     self canAccept ifFalse: [^ false].
> +     self checkOrCorrectSuffix ifFalse: [^ false].
> +     ^ self basicAcceptFileName
> 
> FileAbstractSelectionDialog>>acceptFileName: {filename} · ct 5/20/2022 12:52
> + acceptFileName: aStringOrText
> +
> +     self selectFileName: aStringOrText.
> +     ^ self acceptFileName
> 
> FileAbstractSelectionDialog>>basicAcceptFileName {accessing} · ct 5/20/2022 12:32
> + basicAcceptFileName
> +     "Accept the file name without checking for patterns or suffices."
> +
> +     self canAccept ifFalse: [^ false].
> +     finalChoice := fileName.
> +     self changed: #close.
> +     ^ true
> 
> FileAbstractSelectionDialog>>basicAcceptFileName: {filename} · ct 5/20/2022 12:54
> + basicAcceptFileName: aStringOrText
> +     "Allow the user to press Cmd + S instead of enter to enforce a file name that does not match the pattern/suffx requirements."
> +
> +     self selectFileName: aStringOrText.
> +     ^ self basicAcceptFileName
> 
> FileAbstractSelectionDialog>>buildButtonsWith: {toolbuilder} · ct 5/19/2022 14:06 (changed)
> buildButtonsWith: builder
> 
>     ^ {
>         builder pluggableButtonSpec new
>                 model: self;
>                 label: 'Accept' translated;
>                 color: (self userInterfaceTheme get: #okColor for: #DialogWindow);
> -                 action: #acceptFileName.
> +                 action: #acceptFileName;
> +                 enabled: #canAccept;
> +                 yourself.
>         builder pluggableButtonSpec new
>                 model: self;
>                 label: 'Cancel' translated;
>                 color: (self userInterfaceTheme get: #cancelColor for: #DialogWindow);
> -                 action: #cancelFileChooser}
> +                 action: #cancelFileChooser;
> +                 yourself}
> 
> FileAbstractSelectionDialog>>buildDirectoryTreeWith: {toolbuilder} · mt 2/10/2022 10:13 (changed)
> buildDirectoryTreeWith: builder
>     | treeSpec |
>     treeSpec := builder pluggableTreeSpec new.
>     treeSpec
>          model: self ;
>          roots: #rootDirectoryList ;
>          hasChildren: #hasMoreDirectories: ;
>          getChildren: #subDirectoriesOf: ;
>          getSelectedPath: #selectedPath ;
>          setSelected: #setDirectoryTo: ;
>          getSelected: #directory;
>          label: #directoryNameOf: ;
>          menu: nil ;
>          autoDeselect: false .
>     ^ treeSpec
> 
> FileAbstractSelectionDialog>>buildFileListWith: {toolbuilder} · ct 5/20/2022 12:08 (changed)
> - buildFileListWith: builder
> + buildFileListWith: builder
> +
>     | listSpec |
>     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) .
> -     ^listSpec
> +         model: self;
> +         list: #fileList;
> +         getIndex: #fileListIndex;
> +         setIndex: #fileListIndex:;
> +         doubleClick: #acceptFileName;
> +         keyPress: nil;
> +         frame:
> +             (self
> +                 frameOffsetFromTop:0
> +                 fromLeft: 0
> +                 width: 1
> +                 bottomFraction: 1).
> +     ^ listSpec
> 
> FileAbstractSelectionDialog>>buildTextInputWith: {toolbuilder} · ct 5/20/2022 13:04 (changed)
> buildTextInputWith: builder
>     | textSpec |
>     textSpec := builder pluggableInputFieldSpec new.
>     textSpec
>         model: self;
>         name: #inputText ;
> -         font: self textViewFont;
>         getText: #inputText;
> -         setText: #selectFilename:;
> -         selection: #contentsSelection.
> -     ^textSpec
> +         editText: #selectFileName:;
> +         setText: #basicAcceptFileName:;
> +         selection: #contentsSelection;
> +         help: 'Enter a filename here or choose from list' translated.
> +     ^textSpec
> 
> 
> FileAbstractSelectionDialog>>buildWith: {toolbuilder} · ct 5/19/2022 23:58 (changed)
> buildWith: builder
> -     "assemble the spec for the common chooser/saver dialog UI"
> 
> -     ^self subclassResponsibility
> +     | windowSpec window |
> +     windowSpec := self buildWindowWith: builder specs: {
> +         (self topConstantHeightFrame: self textViewHeight
> +             fromLeft: 0
> +             width: 1) -> [self buildTextInputWith: builder].
> +         (self frameOffsetFromTop: self textViewHeight
> +             fromLeft: 0.35
> +             width: 0.65
> +             offsetFromBottom: 0) -> [self buildFileListWith: builder].
> +         (self frameOffsetFromTop: self textViewHeight
> +             fromLeft: 0
> +             width: 0.35
> +             offsetFromBottom: 0) -> [self buildDirectoryTreeWith: builder].
> +     }.
> +     windowSpec buttons addAll: ( self buildButtonsWith: builder ).
> +     window := builder build: windowSpec.
> +     (window respondsTo: #addKeyboardCaptureFilter:) ifTrue: [
> +         window addKeyboardCaptureFilter: self].
> +     self changed: #selectedPath.
> +     self inputText: fileName.
> +     (window respondsTo: #positionOverWidgetNamed:) ifTrue: [
> +         window positionOverWidgetNamed: #inputText].
> +     ^window
> 
> FileAbstractSelectionDialog>>canAccept {accessing} · ct 5/20/2022 11:53
> + canAccept
> +
> +     ^ fileName isEmptyOrNil not
> +         and: [self directory fileExists: fileName]
> 
> FileAbstractSelectionDialog>>cancelFileChooser {accessing} · tpr 12/23/2017 12:35 (changed and recategorized)
> cancelFileChooser
>     "User clicked to cancel the current state so nil the filename and close the dialog"
> 
>     directory := finalChoice := fileName := nil.
>     self changed: #close.
> 
> FileAbstractSelectionDialog>>checkOrCorrectSuffix {filename} · ct 5/20/2022 12:33
> + checkOrCorrectSuffix
> +
> +     ^ patternList anySatisfy: [:each |
> +         each match: fileName "caseSensitive: FileDirectory default isCaseSensitive"]
> 
> FileAbstractSelectionDialog>>contentsSelection {toolbuilder} · ct 5/20/2022 13:18
> + contentsSelection
> +     "Initial selection covers entire initial file name/path if any"
> +
> +     ^ 1 to: (self inputText ifNil: [0] ifNotNil: #size)
> 
> FileAbstractSelectionDialog>>directory {directory tree} · tpr 11/21/2017 09:22 (changed)
> directory
>     "If nobody has set a specific directory we need a plausible default"
> 
>     ^ directory ifNil: [ directory := FileDirectory default]
> 
> FileAbstractSelectionDialog>>directory: {directory tree} · tpr 11/20/2017 18:15 (changed)
> directory: aFileDirectory
>     "Set the path of the directory to be displayed in the directory tree pane"
> 
>     directory := aFileDirectory
> 
> FileAbstractSelectionDialog>>entriesMatching: {file list} · ct 5/20/2022 12:44 (changed)
> entriesMatching: patternList
>     "Answer a list of directory entries which match any of the patterns.
>     See #parsePatternString for the pattern rules"
> 
>     | entries |
> -     "This odd clause helps supports MVC projects; the file list & directory views are built from a list that includes directories. In Morphic we filter out the directories because they are entirely handled by the direcctory tree morph"
>     entries := Smalltalk isMorphic
> -         ifTrue:[self directory fileEntries ]
> -         ifFalse:[self directory entries].
> -
> +         ifTrue: [self directory fileEntries]
> +         ifFalse:
> +             [self flag: #mvc. "This odd clause helps supports MVC projects; the file list & directory views are built from a list that includes directories. In Morphic we filter out the directories because they are entirely handled by the direcctory tree morph"
> +             self directory entries copyWith:
> +                 (self directory entryAt: self directory class parentDirectoryNickname)].
> +     
>     (patternList anySatisfy: [:each | each = '*'])
>         ifTrue: [^ entries].
> 
> -     ^ entries select: [:entry | patternList anySatisfy: [:each | each match: entry name]]
> +     ^ entries select: [:entry |
> +         patternList anySatisfy: [:each |
> +             entry isDirectory or: [each match: entry name]]]
> 
> FileAbstractSelectionDialog>>fileListIndex: {file list} · ct 5/20/2022 12:05 (changed)
> fileListIndex: anInteger
>     "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"
> -
> +     fileName := nameList at: anInteger ifAbsent: [nil].
> +     
> +     (fileName notNil and: [self directory directoryExists: fileName]) ifTrue:
> +         [self flag: #mvc. "file list contains directories"
> +         self setDirectoryTo: (self directory on: (self directory entryAt: fileName) fullName).
> +         self changed: #selectedPath.
> +         fileName := nil].
> +     
>     self
>         changed: #fileListIndex;
> -         changed: #inputText
> +         changed: #inputText;
> +         changed: #canAccept.
> 
> FileAbstractSelectionDialog>>finalChoice {accessing} · tpr 12/23/2017 12:33 (changed and recategorized)
> finalChoice
>     "return the chosen directory/filename that was saved by an accept click or nil; client must check for validity"
>     ^ finalChoice
>         ifNotNil: [self directory fullNameFor: finalChoice]
> 
> FileAbstractSelectionDialog>>initialize {initialize-release} · ct 5/19/2022 17:55 (changed)
> initialize
>     super initialize.
>     directoryCache := WeakIdentityKeyDictionary new.
>     listIndex := 0.
>     patternList := self defaultPatternList.
> -     suffixList := OrderedCollection new
> +     suffixList := OrderedCollection new.
> +     isUpdating := false.
> 
> FileAbstractSelectionDialog>>listForPatterns: {path and pattern} · ct 5/19/2022 22:18 (changed)
> listForPatterns: arrayOfPatterns
>     "build lists of name, date and size for those file names which match any of the patterns in the array.
>     We use a Set to avoid duplicates and sort them by name"
> 
>     | newList |
>     newList := Set new.
>     newList addAll: (self entriesMatching: arrayOfPatterns).
> 
>     newList := newList sorted: [:a :b|
>                             a name <= b name].
>     nameList := newList collect:[:e| e name].
> +     
> +     self flag: #dead. "dates and sizes are not in use"
>     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]
> +     sizeList := newList collect:[:e| e fileSize asStringWithCommas].
> 
> 
> FileAbstractSelectionDialog>>newDirectoryName {directory tree} · ct 5/19/2022 21:43 (changed)
> newDirectoryName
>     "Create a new directory; will be a subdirectory of the current chosen directory.
>     If the user input is empty, or if the directory creation fails, fail this method.
>     Update the directory tree display afterwards and set the current directory to the newly created directory"
>     |userInput|
> -     userInput := UIManager default request: 'New directory name' translated initialAnswer: 'newDir'.
> +     userInput := Project uiManager request: 'New directory name' translated initialAnswer: 'newDir'.
>     userInput isEmptyOrNil ifTrue: [^nil].
>     
>     [self directory createDirectory: userInput] ifError:[^nil]. "I hate using ifError: - it's so indiscriminate. Really ought to be a more precise error to catch properly"
> -
> +     
>     self changed: #rootDirectoryList.
>     self directory: (self directory / userInput).
>     self changed: #selectedPath
> 
> FileAbstractSelectionDialog>>selectFileName: {filename} · ct 5/20/2022 12:52
> + selectFileName: aText
> +
> +     | result |
> +     fileName := aText asString.
> +     
> +     (directory class dirPathFor: aText asString) ifNotEmpty: [:otherDirPath |
> +         ([directory on: otherDirPath] ifError: [nil]) ifNotNil: [:otherDir |
> +             otherDir exists ifTrue:
> +                 [self setDirectoryTo: otherDir.
> +                 self changed: #selectedPath.
> +                 fileName := directory class localNameFor: aText asString.
> +                 self changed: #inputText.
> +                 self changed: #canAccept.
> +                 ^ self selectFileName: fileName]]].
> +     
> +     result := self selectExistingFileName.
> +     self changed: #canAccept.
> +     ^ result
> 
> FileAbstractSelectionDialog>>setDirectoryTo: {directory tree} · ct 5/19/2022 23:54 (changed)
> 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].
> +     dir isString ifTrue: [
> +         self flag: #mvc. "PluggableListView auto-converts all items to strings :("
> +         ^ self setDirectoryTo: (FileDirectory on: (Scanner new scanTokens: dir) third)].
> "okToChange is probably redundant.
> modelSleep/Wake is related to use of ServerDirectories, which are not yet hooked up"
>     self okToChange ifFalse: [ ^ self ].
>     self modelSleep.
>     self directory: dir.
>     self modelWakeUp.
>     self changed: #directory.
>     self updateFileList.
> -     self changed: #inputText
> +     isUpdating ifFalse: [self changed: #inputText].
> 
> FileAbstractSelectionDialogTest
> + ClassTestCase subclass: #FileAbstractSelectionDialogTest
> +     instanceVariableNames: 'mockDirectory mockFile1 mockFile2 mockFile3 mockChildDirectory mockChildFile dialog morph result'
> +     classVariableNames: ''
> +     poolDictionaries: ''
> +     category: 'MorphicTests-ToolBuilder'
> +
> + FileAbstractSelectionDialogTest class
> +     instanceVariableNames: ''
> +
> + ""
> 
> FileAbstractSelectionDialogTest class>>isAbstract {testing } · ct 5/19/2022 20:57
> + isAbstract
> +
> +     ^ self = FileAbstractSelectionDialogTest
> 
> FileAbstractSelectionDialogTest>>classToBeTested {accessing} · ct 5/19/2022 20:20
> + classToBeTested
> +
> +     ^ self dialogClass
> 
> FileAbstractSelectionDialogTest>>createChildFile: {support} · ct 5/19/2022 21:34
> + createChildFile: fileName
> +
> +     FileStream
> +         fileNamed: (self pathForChildFile: fileName)
> +         do: [:stream | stream nextPutAll: thisContext longPrintString].
> 
> FileAbstractSelectionDialogTest>>createFile: {support} · ct 5/19/2022 21:34
> + createFile: fileName
> +
> +     FileStream
> +         fileNamed: (self pathForFile: fileName)
> +         do: [:stream | stream nextPutAll: thisContext longPrintString].
> 
> FileAbstractSelectionDialogTest>>dialogClass {accessing} · ct 5/19/2022 20:20
> + dialogClass
> +
> +     ^ self targetClass
> 
> FileAbstractSelectionDialogTest>>openDialog {support} · ct 5/19/2022 20:40
> + openDialog
> +
> +     ^ morph := self toolBuilder build: dialog
> 
> FileAbstractSelectionDialogTest>>pathForChildFile: {support} · ct 5/19/2022 21:31
> + pathForChildFile: fileName
> +
> +     ^ self pathForDirectory: mockChildDirectory file: fileName
> 
> FileAbstractSelectionDialogTest>>pathForDirectory:file: {support} · ct 5/19/2022 21:30
> + pathForDirectory: directory file: fileName
> +
> +     ^ directory fullName, directory class slash, fileName
> 
> FileAbstractSelectionDialogTest>>pathForFile: {support} · ct 5/19/2022 21:30
> + pathForFile: fileName
> +
> +     ^ self pathForDirectory: mockDirectory file: fileName
> 
> FileAbstractSelectionDialogTest>>setUp {running} · ct 5/19/2022 20:45
> + setUp
> +
> +     super setUp.
> +     
> +     self setUpDirectory.
> +     self setUpDialog.
> 
> FileAbstractSelectionDialogTest>>setUpDialog {running} · ct 5/19/2022 20:42
> + setUpDialog
> +
> +     dialog := self dialogClass new.
> +     dialog addDependent: self.
> +     dialog directory: mockDirectory.
> 
> FileAbstractSelectionDialogTest>>setUpDirectory {running} · ct 5/19/2022 21:36
> + setUpDirectory
> +
> +     mockDirectory := FileDirectory default / self class asString / UUID new asString.
> +     mockDirectory assureExistence.
> +     
> +     {mockFile1 := 'plonk1.txt'.
> +     mockFile2 := 'plonk2.st'.
> +     mockFile3 := 'plonk3.cs'}
> +         do: [:file | self createFile: file].
> +     
> +     mockChildDirectory := mockDirectory / 'child'.
> +     mockChildDirectory assureExistence.
> +     
> +     mockChildFile := 'griffle.gif'.
> +     self createChildFile: mockChildFile.
> 
> FileAbstractSelectionDialogTest>>tearDown {running} · ct 5/20/2022 13:25
> + tearDown
> +
> +     [mockDirectory ifNotNil: [mockDirectory assureAbsence].
> +     (FileDirectory default / self class asString) assureAbsence]
> +         ensure: [super tearDown].
> 
> FileAbstractSelectionDialogTest>>testCancel {tests - interface} · ct 5/19/2022 22:17
> + testCancel
> +
> +     self openDialog.
> +     
> +     dialog cancelFileChooser.
> +     
> +     self assert: nil equals: result.
> 
> FileAbstractSelectionDialogTest>>testDirectoryTree {tests - interface} · ct 5/19/2022 22:20
> + testDirectoryTree
> +
> +     | node index |
> +     self openDialog.
> +     
> +     node := dialog rootDirectoryList
> +         detect: [:root | (dialog directoryNameOf: root) = mockDirectory pathParts first]
> +         ifNone: [].
> +     self assert: node notNil.
> +     
> +     index := 2.
> +     [self assert: (dialog hasMoreDirectories: node).
> +     (dialog subDirectoriesOf: node)
> +         detect: [:child | (dialog directoryNameOf: child) = (mockDirectory pathParts at: index)]
> +         ifFound: [:child | node := child. index := index + 1]
> +         ifNone: [self fail]]
> +             doWhileFalse: [(dialog directoryNameOf: node) = mockDirectory localName].
> +     self assert: mockDirectory equals: dialog directory.
> +     
> +     self deny: (dialog hasMoreDirectories: mockChildDirectory).
> 
> FileAbstractSelectionDialogTest>>toolBuilder {accessing} · ct 5/19/2022 20:25
> + toolBuilder
> +
> +     ^ self toolBuilderClass new
> 
> FileAbstractSelectionDialogTest>>toolBuilderClass {accessing} · ct 5/19/2022 20:25
> + toolBuilderClass
> +
> +     ^ MorphicToolBuilder
> 
> FileAbstractSelectionDialogTest>>update: {updating} · ct 5/19/2022 20:41
> + update: aspect
> +
> +     aspect = #close ifTrue:
> +         [result := dialog finalChoice].
> 
> FileChooserDialog>>inputText {filename} · ct 5/19/2022 14:02
> + inputText
> +     "return the filename to appear in the text field"
> +
> +     ^fileName
> 
> FileChooserDialog>>inputText: {filename} · ct 5/20/2022 13:06
> + inputText: aText
> +     "Initialize the filename entry field to aString. If a file with that name already exists, set up to highlight it."
> +     aText ifNil: [^ self].
> +     fileName := aText asString.
> +     self selectExistingFileName
> 
> FileChooserDialog>>selectExistingFileName {private} · ct 5/20/2022 13:06
> + selectExistingFileName
> +     "Answer whether an existing file in the list matches my proposed filename, selecting it if it does."
> +
> +     listIndex := nameList findFirst: [:each |
> +         fileName isEmptyOrNil not and:
> +             [each beginsWith: fileName "caseSensitive: FileDirectory default isCaseSensitive"]].
> +     fileName := nameList at: listIndex ifAbsent: [nil].
> +     
> +     self changed: #fileListIndex.
> +     self changed: #canAccept.
> +     
> +     ^ listIndex ~= 0
> 
> FileChooserDialog>>userMessage {ui details} · ct 5/19/2022 17:40 (changed)
> userMessage
> -     "return the string to present to the user in order to explain the purpose of this dialog appearing"
> +     "return the string to present to the user in order to explain the purpose of this dialog appearing"
>     
>     ^message ifNil: ['Choose a file name' translated]
> 
> FileChooserDialogTest
> + nil subclass: #FileChooserDialogTest
> +     instanceVariableNames: ''
> +     classVariableNames: ''
> +     poolDictionaries: ''
> +     category: 'MorphicTests-ToolBuilder'
> +
> + FileChooserDialogTest class
> +     instanceVariableNames: ''
> +
> + ""
> 
> FileChooserDialogTest>>testTypeToSelect {tests - interactions} · ct 5/20/2022 12:54
> + testTypeToSelect
> +
> +     self openDialog.
> +     
> +     dialog selectFileName: mockFile2 allButLast.
> +     self assert: mockFile2 equals: (dialog fileList at: dialog fileListIndex).
> +     dialog acceptFileName.
> +     
> +     self assert: (self pathForFile: mockFile2) equals: result.
> 
> FileDialogTest
> + nil subclass: #FileDialogTest
> +     instanceVariableNames: ''
> +     classVariableNames: ''
> +     poolDictionaries: ''
> +     category: 'MorphicTests-ToolBuilder'
> +
> + FileDialogTest class
> +     instanceVariableNames: ''
> +
> + ""
> 
> FileDialogTest class>>isAbstract {testing } · ct 5/19/2022 20:57
> + isAbstract
> +
> +     ^ self = FileDialogTest
> 
> FileDialogTest>>testChooseAbsentFile {tests - interface} · ct 5/20/2022 12:53
> + testChooseAbsentFile
> +
> +     self openDialog.
> +     
> +     dialog acceptFileName: mockFile2 , '.absent'.
> +     
> +     self assert: nil equals: result.
> 
> FileDialogTest>>testChooseFile {tests - interface} · ct 5/20/2022 12:53
> + testChooseFile
> +
> +     self openDialog.
> +     
> +     dialog acceptFileName: mockFile2.
> +     
> +     self assert: (self pathForFile: mockFile2) equals: result.
> 
> FileDialogTest>>testChooseNothing {tests - interactions} · ct 5/19/2022 21:03
> + testChooseNothing
> +
> +     self openDialog.
> +     
> +     self deny: dialog canAccept.
> +     dialog acceptFileName.
> +     
> +     self assert: nil equals: result.
> 
> FileDialogTest>>testFileList {tests - interface} · ct 5/19/2022 20:47
> + testFileList
> +
> +     self openDialog.
> +     
> +     self assert: {mockFile1. mockFile2. mockFile3} equals: dialog fileList.
> 
> FileDialogTest>>testFileListWithPattern {tests - interface} · ct 5/19/2022 20:47
> + testFileListWithPattern
> +
> +     dialog pattern: '*2*'.
> +     self openDialog.
> +     
> +     self assert: {mockFile2} equals: dialog fileList.
> 
> FileDialogTest>>testFileListWithSuffixList {tests - interface} · ct 5/19/2022 20:49
> + testFileListWithSuffixList
> +
> +     dialog suffixList: #('cs' 'st').
> +     self openDialog.
> +     
> +     self assert: {mockFile2. mockFile3} equals: dialog fileList.
> 
> FileDialogTest>>testSelectAndChooseFile {tests - interactions} · ct 5/19/2022 21:03
> + testSelectAndChooseFile
> +
> +     self openDialog.
> +     
> +     dialog fileListIndex: (dialog fileList indexOf: mockFile2).
> +     self assert: dialog canAccept.
> +     dialog acceptFileName.
> +     
> +     self assert: (self pathForFile: mockFile2) equals: result.
> 
> FileDialogTest>>testSelectAndChooseSubFile {tests - interactions} · ct 5/19/2022 21:35
> + testSelectAndChooseSubFile
> +
> +     self openDialog.
> +     
> +     dialog setDirectoryTo: (dialog subDirectoriesOf: dialog directory) first.
> +     self assert: {mockChildFile} equals: dialog fileList.
> +     dialog fileListIndex: (dialog fileList indexOf: mockChildFile).
> +     self assert: dialog canAccept.
> +     dialog acceptFileName.
> +     
> +     self assert: (self pathForChildFile: mockChildFile) equals: result.
> 
> FileDialogTest>>testTypeAndChooseAbsentFile {tests - interactions} · ct 5/20/2022 12:54
> + testTypeAndChooseAbsentFile
> +
> +     self openDialog.
> +     
> +     dialog selectFileName: mockFile2 , '.absent'.
> +     self deny: dialog canAccept.
> +     dialog acceptFileName.
> +     
> +     self assert: nil equals: result.
> 
> FileDialogTest>>testTypeAndChooseFile {tests - interactions} · ct 5/20/2022 12:55
> + testTypeAndChooseFile
> +
> +     self openDialog.
> +     
> +     dialog selectFileName: mockFile2.
> +     self assert: dialog canAccept.
> +     dialog acceptFileName.
> +     
> +     self assert: (self pathForFile: mockFile2) equals: result.
> 
> FileDialogTest>>testTypeAndChooseFullPath {tests - interactions} · ct 5/20/2022 12:55
> + testTypeAndChooseFullPath
> +
> +     self openDialog.
> +     
> +     dialog selectFileName: (self pathForChildFile: mockChildFile).
> +     self assert: mockChildDirectory equals: dialog directory.
> +     self assert: dialog canAccept.
> +     dialog acceptFileName.
> +     
> +     self assert: (self pathForChildFile: mockChildFile) equals: result.
> 
> FileSaverDialog>>buildButtonsWith: {toolbuilder} · ct 5/19/2022 23:59 (changed)
> buildButtonsWith: builder
> -     "add a 'new directory' button to the beginning of the row of buttons"
> -     ^{ builder pluggableButtonSpec new
> +
> +     ^ (super buildButtonsWith: builder)
> +         copyWith:
> +             (builder pluggableButtonSpec new
>                 model: self;
>                 label: 'New Directory' translated;
>                 color: (self userInterfaceTheme get: #buttonColor for: #DialogWindow);
> -                 action: #newDirectoryName}, (super buildButtonsWith: builder)
> +                 action: #newDirectoryName;
> +                 yourself)
> 
> FileSaverDialog>>canAccept {accessing} · ct 5/20/2022 11:55
> + canAccept
> +
> +     ^ fileName isEmptyOrNil not
> +         and: [(self directory directoryExists: fileName) not]
> 
> FileSaverDialog>>checkOrCorrectSuffix {filename} · ct 5/20/2022 12:35
> + checkOrCorrectSuffix
> +
> +     | suffix |
> +     super checkOrCorrectSuffix ifTrue: [^ true].
> +     
> +     suffixList ifEmpty: [^ false].
> +     
> +     suffixList size = 1 ifTrue:
> +         [((suffix := '.' , suffixList anyOne)
> +             compare: (fileName last: (suffix size min: fileName size))
> +             caseSensitive: directory isCaseSensitive)
> +                 = 2 ifFalse: [ fileName := fileName , suffix ].
> +         ^ true].
> +     
> +     suffix := (Project uiManager
> +         chooseFrom: suffixList
> +         values: suffixList
> +         title: 'Please choose the type of file to save.' translated)
> +             ifNil: [^ false].
> +     fileName := fileName , '.' , suffix.
> +     self acceptFileName.
> +     ^ true
> 
> FileSaverDialog>>initialFilename: {accessing} · tpr 11/22/2017 16:39 (changed and recategorized)
> initialFilename: aFilenameOrNil
>     "Set the initial choice of filename to highlight.
>     We split the potential filename to see if it includes a path and if so, use that as the chosen directory - the client can manually change that with a subsequent send of #directory: if wanted.
>     We split the root filename to find an extension and use that as the suffix - again, the client can manually change that later"
> 
>     | e f p |
>     aFilenameOrNil ifNil:[^self].
>     
>     p := FileDirectory dirPathFor: aFilenameOrNil.
>     p isEmpty ifFalse:[self directory: (FileDirectory on: p)].    
>     f := FileDirectory localNameFor: aFilenameOrNil.
>     fileName := f.
>     e := FileDirectory extensionFor: f.
>     e isEmpty ifFalse:[self suffix: e]
> 
> FileSaverDialog>>inputText {filename} · ct 5/19/2022 14:01 (changed)
> inputText
>     "return the filename to appear in the text field"
> 
> -     ^fileName ifNil:['Enter a filename here or choose from list' translated]
> +     ^fileName
> 
> FileSaverDialog>>inputText: {filename} · ct 5/20/2022 13:06 (changed)
> inputText: aText
>     "Initialize the filename entry field to aString. If a file with that name already exists, set up to highlight it."
>     aText ifNil: [^ self].
>     fileName := aText asString.
> -     self selectExistingFilename
> +     self selectExistingFileName
> 
> FileSaverDialog>>selectExistingFileName {private} · ct 5/20/2022 13:06
> + selectExistingFileName
> +     "Answer whether an existing file in the list matches my proposed filename, selecting it if it does."
> +
> +     listIndex := nameList findFirst: [:each | each = fileName].
> +     self changed: #fileListIndex.
> +     ^ true
> 
> FileSaverDialogTest
> + nil subclass: #FileSaverDialogTest
> +     instanceVariableNames: ''
> +     classVariableNames: ''
> +     poolDictionaries: ''
> +     category: 'MorphicTests-ToolBuilder'
> +
> + FileSaverDialogTest class
> +     instanceVariableNames: ''
> +
> + ""
> 
> FileSaverDialogTest>>testChooseAbsentFile {tests - interface} · ct 5/20/2022 12:53
> + testChooseAbsentFile
> +
> +     self openDialog.
> +     
> +     dialog acceptFileName: mockFile2 , '.absent'.
> +     
> +     self assert: (self pathForFile: mockFile2 , '.absent') equals: result.
> 
> FileSaverDialogTest>>testChooseAbsentFileWithSuffix {tests - interface} · ct 5/20/2022 12:53
> + testChooseAbsentFileWithSuffix
> +
> +     dialog suffix: 'txt'.
> +     self openDialog.
> +     
> +     dialog acceptFileName: mockFile2 , '.absent'.
> +     
> +     self assert: (self pathForFile: mockFile2 , '.absent.txt') equals: result.
> 
> FileSaverDialogTest>>testChooseDirectory {tests - interface} · ct 5/20/2022 12:53
> + testChooseDirectory
> +
> +     self openDialog.
> +     
> +     dialog acceptFileName: mockChildDirectory localName.
> +     
> +     self deny: dialog canAccept.
> +     self assert: result isNil.
> 
> FileSaverDialogTest>>testInitialFileName {tests - interface} · ct 5/19/2022 21:08
> + testInitialFileName
> +
> +     dialog initialFilename: mockFile2.
> +     self openDialog.
> +     
> +     self assert: mockFile2 equals: dialog inputText.
> +     self assert: mockFile2 equals: (dialog fileList at: dialog fileListIndex).
> +     self assert: dialog canAccept.
> +     dialog acceptFileName.
> +     
> +     self assert: (self pathForFile: mockFile2) equals: result.
> 
> FileSaverDialogTest>>testNewDirectory {tests - interface} · ct 5/20/2022 12:53
> + testNewDirectory
> +
> +     self openDialog.
> +     
> +     [dialog newDirectoryName] valueSupplyingAnswer: #('*name*' 'nurp').
> +     self assert: (mockDirectory directoryExists: 'nurp').
> +     self assert: mockDirectory / 'nurp' equals: dialog directory.
> +     dialog acceptFileName: 'zonk'.
> +     
> +     self assert: (self pathForDirectory: mockDirectory / 'nurp' file: 'zonk') equals: result.
> 
> FileSaverDialogTest>>testTypeAndChooseAbsentFile {tests - interactions} · ct 5/20/2022 12:55
> + testTypeAndChooseAbsentFile
> +
> +     self openDialog.
> +     
> +     dialog selectFileName: mockFile2 , '.absent'.
> +     self assert: dialog canAccept.
> +     dialog acceptFileName.
> +     
> +     self assert: (self pathForFile: mockFile2 , '.absent') equals: result.
> 
> FileSaverDialogTest>>testTypeAndChooseAbsentFileWithSuffixList {tests - interactions} · ct 5/20/2022 12:55
> + testTypeAndChooseAbsentFileWithSuffixList
> +
> +     dialog suffixList: #('txt' 'cs').
> +     self openDialog.
> +     
> +     "suffix choice is cancellable and dialog will remain open"
> +     2 timesRepeat:
> +         [[dialog selectFileName: mockFile2 , '.absent'.
> +         dialog acceptFileName]
> +             valueSupplyingAnswer: #('*type*' cancel)].
> +     
> +     [dialog selectFileName: mockFile2 , '.absent'.
> +     dialog acceptFileName]
> +         valueSupplyingAnswer: #('*type*' 'cs').
> +     
> +     self assert: (self pathForFile: mockFile2 , '.absent.cs') equals: result.
> 
> ---
> Sent from Squeak Inbox Talk [https://github.com/hpi-swa-lab/squeak-inbox-talk]
> ["polish-file-dialogs.3.cs"]
> ["DirectoryChooserDialog.png"]
> ["FileChooserDialog.png"]
> -------------- next part --------------
> An HTML attachment was scrubbed...
> URL: <http://lists.squeakfoundation.org/pipermail/squeak-dev/attachments/20220707/4d4c468a/attachment-0001.html>
> 
> 
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.squeakfoundation.org/pipermail/squeak-dev/attachments/20220709/f0fe226c/attachment-0001.html>


More information about the Squeak-dev mailing list