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