[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
Fri May 20 11:47:31 UTC 2022


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
["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/20220520/938c6d0a/attachment-0001.html>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: polish-file-dialogs.3.cs
Type: application/octet-stream
Size: 37819 bytes
Desc: not available
URL: <http://lists.squeakfoundation.org/pipermail/squeak-dev/attachments/20220520/938c6d0a/attachment-0003.obj>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: DirectoryChooserDialog.png
Type: application/octet-stream
Size: 35020 bytes
Desc: not available
URL: <http://lists.squeakfoundation.org/pipermail/squeak-dev/attachments/20220520/938c6d0a/attachment-0004.obj>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: FileChooserDialog.png
Type: application/octet-stream
Size: 64585 bytes
Desc: not available
URL: <http://lists.squeakfoundation.org/pipermail/squeak-dev/attachments/20220520/938c6d0a/attachment-0005.obj>


More information about the Squeak-dev mailing list