<div id="__MailbirdStyleContent" style="font-size: 10pt;font-family: Arial;color: #000000;text-align: left" dir="ltr">
                                        <img id="4c8eaa62-b0a6-4a55-b785-3fdb846c0280" src="cid:0dab9451-0169-45d5-91f3-93fb716479a9" width="506" height="222"></img><br><div class="mb_sig"></div>
                                        <blockquote class="history_container" type="cite" style="border-left-style: solid;border-width: 1px;margin-top: 20px;margin-left: 0px;padding-left: 10px;min-width: 500px">
                        <p style="color: #AAAAAA; margin-top: 10px;">Am 25.11.2021 11:49:07 schrieb commits@source.squeak.org <commits@source.squeak.org>:</p><div style="font-family:Arial,Helvetica,sans-serif">Marcel Taeumel uploaded a new version of Tools to project The Trunk:<br>http://source.squeak.org/trunk/Tools-mt.1075.mcz<br><br>==================== Summary ====================<br><br>Name: Tools-mt.1075<br>Author: mt<br>Time: 25 November 2021, 11:48:52.151354 am<br>UUID: 089e5a95-68d9-1a44-957c-5ddbc897d40e<br>Ancestors: Tools-mt.1074<br><br>Refactors and extends "save contents to file" feature in workspaces and all kinds of tools that use PluggableTextMorph's (with their TextEditor). This also simplifies the rather recent "file-out workspace contents on accept" feature (see preferences).<br><br>Thanks to Eliot (eem) for the idea and change set that served as a valuable template for this refactoring.<br><br>Thanks to Christoph (ct) for pointing out the strange nature of that "file-out file path" preference.<br><br>Note that all open workspaces should be updated automatically. See postscript.<br><br>=============== Diff against Tools-mt.1074 ===============<br><br>Item was changed:<br>  ----- Method: FileList>>viewContentsInWorkspace (in category 'own services') -----<br>  viewContentsInWorkspace<br>+       "View the contents of my selected file in a new workspace."<br>-        "View the contents of my selected file in a new workspace"<br>          <br>+     | fileContents workspace lineConversion |<br>+    fileContents := self directory<br>+               readOnlyFileNamed: self fileName<br>+             do: [:fileStream |<br>+                   fileStream<br>+                           setConverterForCode;<br>+                                 wantsLineEndConversion: true.<br>+                        lineConversion := fileStream detectLineEndConvention.<br>+                        fileStream contents].           <br>+     workspace := (Project uiManager edit: fileContents label: nil shouldStyle: Workspace shouldStyle) model.<br>+     <br>+     "Remember certain information to allow edits in the same file."<br>+    workspace<br>+            windowTitle: (self directory localNameFor: self fileName);<br>+           fileDirectory: self directory;<br>+               setProperty: #fileLineConversion toValue: lineConversion;<br>+            saveContentsInFileOnAccept.<br>+ !<br>-     | aFileStream aName lineConversion w | <br>-      aFileStream := (directory readOnlyFileNamed: self fullName) setConverterForCode.<br>-     aFileStream wantsLineEndConversion: true.<br>-    lineConversion := aFileStream detectLineEndConvention.<br>-       aName := aFileStream localName.<br>-      w := UIManager default<br>-                       edit: ([aFileStream contentsOfEntireFile] ensure: [aFileStream close])<br>-                       label: ((aName includesSubstring: 'Workspace')<br>-                                       ifTrue: [#('.text' '.txt') inject: aName into: [:name :ext| (name endsWith: ext) ifTrue: [name allButLast: ext size] ifFalse: [name]]]<br>-                                       ifFalse: ['Workspace from ', aName]).<br>-        w setProperty: #lineConversion toValue: lineConversion.<br>-      directory ~= FileDirectory default ifTrue: [w setProperty: #myDir toValue: directory]!<br><br>Item was removed:<br>- ----- Method: Workspace class>>fileOut: (in category 'support') -----<br>- fileOut: contents<br>-      "Write the given contents into the workspace file-out file path."<br>- <br>-      | filePath |<br>-         filePath := self fileOutFilePath.<br>-    (FileDirectory default on: filePath) containingDirectory assureExistence.<br>-    FileStream<br>-           fileNamed: filePath<br>-          do: [:stream |<br>-                       stream<br>-                               setToEnd;<br>-                            nextPutAll: '"----ACCEPT----';<br>-                          nextPutAll: DateAndTime now asString;<br>-                                nextPutAll: '"';<br>-                                cr; nextPutAll: contents; cr].<br>-       Transcript showln: 'Workspace contents successfully appended to: ', filePath printString.!<br><br>Item was removed:<br>- ----- Method: Workspace class>>fileOutFilePath (in category 'preferences') -----<br>- fileOutFilePath<br>-         <preference: 'file-out="" file="" path="" for="" workspace'=""></preference:><br>-          categoryList: #('browsing' 'tools')<br>-          description: 'Set the file-out location for #fileOutOnAccept in workspaces.' <br>-                type: #String><br>-    ^ FileOutFilePath ifNil: [ 'workspace.st' ]!<br><br>Item was removed:<br>- ----- Method: Workspace class>>fileOutFilePath: (in category 'preferences') -----<br>- fileOutFilePath: aString<br>- <br>-         FileOutFilePath := aString.!<br><br>Item was changed:<br>  ----- Method: Workspace class>>fileOutOnAccept (in category 'preferences') -----<br>  fileOutOnAccept<br>        <preference: 'file-out="" workspace="" contents="" on="" accept'=""></preference:><br>              categoryList: #('browsing' 'tools')<br>+          description: 'If true, accepting contents in a workspace will append them to a file on disk. The file name is derived from the workspace''s current window title and can thus be changed. The default name is thus ''Workspace.text''.'<br>-              description: 'If true, accepting contents in a workspace will append them to a known location in the file system. See #fileOutFilePath.' <br>             type: #Boolean><br>    ^ FileOutOnAccept ifNil: [ true ]!<br><br>Item was changed:<br>  ----- Method: Workspace class>>open (in category 'instance creation') -----<br>  open<br>  <br>      | workspace |<br>         workspace := self new.<br>+       self fileOutOnAccept<br>+                 ifTrue: [workspace appendContentsToFileOnAccept].<br>-    self fileOutOnAccept ifTrue: [<br>-               workspace acceptAction: [:string | self fileOut: string]].<br>    ^ self embedTranscript<br>                ifTrue: [workspace buildAndOpenWorkspaceTranscript]<br>           ifFalse: [workspace buildAndOpen]!<br><br>Item was changed:<br>  ----- Method: Workspace>>acceptContents: (in category 'accessing') -----<br>  acceptContents: aString<br>+ <br>+     ^ (acceptAction ifNotNil: [acceptAction value: aString]) ~~ false<br>+            and: [super acceptContents: aString]!<br>-        acceptAction ifNotNil:[acceptAction value: aString].<br>-         ^super acceptContents: aString.!<br><br>Item was changed:<br>  ----- Method: Workspace>>addModelItemsToWindowMenu: (in category 'menu commands') -----<br>  addModelItemsToWindowMenu: aMenu <br>   <br>      aMenu addLine.<br>        aMenu<br>+                add: 'change file name...'<br>+           target: self<br>+                 action: #changeFileName.<br>+     aMenu<br>                 add: 'save contents to file...'<br>               target: self<br>                  action: #saveContentsInFile.<br>          aMenu<br>+                addUpdating: #saveContentsInFileOnAcceptWording<br>+              target: self<br>+                 action: #saveContentsInFileOnAccept.<br>+         aMenu<br>+                addUpdating: #appendContentsToFileOnAcceptWording<br>+            target: self<br>+                 action: #appendContentsToFileOnAccept.<br>+       aMenu addLine.<br>+       aMenu<br>                 add: 'inspect variables'<br>              target: self<br>                  action: #inspectBindings.<br>     aMenu<br>                 add: 'reset variables'<br>                target: self<br>+                 action: #resetBindings.<br>+      aMenu addLine.<br>-               action: #initializeBindings.<br>          aMenu<br>                 addUpdating: #mustDeclareVariableWording<br>              target: self<br>                  action: #toggleVariableDeclarationMode.<br>       aMenu<br>                 addUpdating: #acceptDroppedMorphsWording<br>              target: self<br>                  action: #toggleDroppingMorphForReference.<br>  <br>         self addToggleStylingMenuItemTo: aMenu.<br>  !<br><br>Item was added:<br>+ ----- Method: Workspace>>appendContentsToFileOnAccept (in category 'menu commands') -----<br>+ appendContentsToFileOnAccept<br>+   "Arrange that the contents will be appended to a file when the user accepts."<br>+ <br>+  self saveContentsInFileOnAcceptEnabled<br>+               ifTrue: [(Project uiManager confirm: 'Do you really want to change file access mode\from #update to #append?\\You might corrupt data when accepting changes.' withCRs translated)<br>+                    ifFalse: [^ self]].<br>+ <br>+      self acceptAction: (self appendContentsToFileOnAcceptEnabled ifTrue: [ "no action" ] ifFalse: [<br>+            [:freshContents | | fileName stringToAppend |<br>+                        "Ensure to compute fileName as late as possible to consider recent changes of the #windowTitle."<br>+                   fileName := self suggestedFileNameForSave.<br>+                   <br>+                     stringToAppend := '"----ACCEPT----{1}"\{2}\' withCRs<br>+                               format: { DateAndTime now asString. freshContents }.<br>+                         <br>+                     ((FileDirectory forFileName: fileName) fileExists: fileName)<br>+                                 ifFalse: [ "If the default file name, which is derived from the current window title, does not exist, ask the user once to confirm the location."             <br>+                                     self<br>+                                                 saveContents: stringToAppend<br>+                                                 accessMode: #create]<br>+                                 ifTrue: [ "Update/replace the contents in the existing file."<br>+                                      self<br>+                                                 saveContents: stringToAppend<br>+                                                 onFileNamed: fileName<br>+                                                accessMode: #append]] ]).!<br><br>Item was added:<br>+ ----- Method: Workspace>>appendContentsToFileOnAcceptEnabled (in category 'menu commands') -----<br>+ appendContentsToFileOnAcceptEnabled<br>+ <br>+   ^ (acceptAction notNil and: [acceptAction home selector = #appendContentsToFileOnAccept])!<br><br>Item was added:<br>+ ----- Method: Workspace>>appendContentsToFileOnAcceptWording (in category 'menu commands') -----<br>+ appendContentsToFileOnAcceptWording<br>+ <br>+   ^ (self appendContentsToFileOnAcceptEnabled<br>+          ifTrue: ['<yes> append contents to {1} on accept']<br>+             ifFalse: ['<no> append contents to {1} on accept']) translated<br>+                         format: { self suggestedFileNameForSave contractTo: 32 }!<br><br>Item was added:<br>+ ----- Method: Workspace>>changeFileName (in category 'menu commands') -----<br>+ changeFileName<br>+  "Let the user specify a new file name (and path) for save-contents requests."<br>+      <br>+     (Project uiManager<br>+           saveFilenameRequest: 'Save text contents in file...'<br>+                 initialAnswer: self suggestedFileNameForSave)<br>+                        ifNotNil: [:newFileName |<br>+                            newFileName ifNotEmpty: [self setFileName: newFileName]].!<br><br>Item was added:<br>+ ----- Method: Workspace>>defaultFileNameForSave (in category 'user edits') -----<br>+ defaultFileNameForSave<br>+    "Overwritten to combine selected properties. Also see FileList >> #viewContentsInWorkspace to understand where different directories might originate."<br>+ <br>+   ^ self fileDirectory fullNameFor: self windowTitle!<br><br>Item was added:<br>+ ----- Method: Workspace>>fileDirectory (in category 'accessing') -----<br>+ fileDirectory<br>+      "Answer the current directory for save-contents requests."<br>+         <br>+     ^ self valueOfProperty: #fileDirectory ifAbsent: [FileDirectory default]!<br><br>Item was added:<br>+ ----- Method: Workspace>>fileDirectory: (in category 'accessing') -----<br>+ fileDirectory: aDirectory<br>+   "Do not save the default directory so that the image and its surrounding files can be moved across the disk."<br>+      <br>+     aDirectory = FileDirectory default<br>+           ifTrue: [self removeProperty: #fileDirectory]<br>+                ifFalse: [self setProperty: #fileDirectory toValue: aDirectory].!<br><br>Item was added:<br>+ ----- Method: Workspace>>saveContents:onFileNamed:accessMode: (in category 'user edits') -----<br>+ saveContents: stringContents onFileNamed: fileName accessMode: accessMode<br>+    "Overwritten to set conversion rule of line-end character. See FileList >> #viewContentsInWorkspace."<br>+        <br>+     ^ self<br>+               saveContents: stringContents<br>+                 onFileNamed: fileName<br>+                accessMode: accessMode<br>+               workBlock: [:fileStream |<br>+                    fileStream<br>+                           lineEndConvention: (self valueOfProperty: #fileLineConversion); "nil is fine here..."<br>+                              nextPutAll: stringContents]!<br><br>Item was added:<br>+ ----- Method: Workspace>>saveContents:onFileNamed:accessMode:workBlock: (in category 'user edits') -----<br>+ saveContents: stringContents onFileNamed: fileName accessMode: accessMode workBlock: workBlock<br>+  "Overwritten to update #fileDirectory property and #windowTitle on success."<br>+       <br>+     ^ (super<br>+             saveContents: stringContents<br>+                 onFileNamed: fileName<br>+                accessMode: accessMode<br>+               workBlock: workBlock)<br>+                        ifFalse: [false "no success"]<br>+                      ifTrue: [self setFileName: fileName. true "success"]!<br><br>Item was changed:<br>  ----- Method: Workspace>>saveContentsInFile (in category 'menu commands') -----<br>  saveContentsInFile<br>+  "Save the view's current contents in a file. Dispatch goes through the view (or morph). See commentary in and senders of #saveContents:accessMode:."<br>-       "Pass along this message to the controller or morph.  (Possibly this Workspace menu item could be deleted, since it's now in the text menu.)"<br>  <br>+  self changed: #saveContents.!<br>-        self changed: #saveContents<br>- !<br><br>Item was added:<br>+ ----- Method: Workspace>>saveContentsInFileOnAccept (in category 'menu commands') -----<br>+ saveContentsInFileOnAccept<br>+   "Arrange that the contents will be saved to a file on each save (or accept). Replace any existing file contents."<br>+ <br>+      self flag: #discuss. "mt: Is it 'onFile' or rather 'inFile'? Note that there are different access modes."<br>+ <br>+      self appendContentsToFileOnAcceptEnabled<br>+             ifTrue: [(Project uiManager confirm: 'Do you really want to change file access mode\from #append to #update?\\You might lose data when accepting changes.' withCRs translated)<br>+                       ifFalse: [^ self]].<br>+  <br>+     self acceptAction: (self saveContentsInFileOnAcceptEnabled<br>+           ifFalse: [ [:stringToSave | | fileName |<br>+                     "Ensure to compute fileName as late as possible to consider recent changes of the #windowTitle."<br>+                   fileName := self suggestedFileNameForSave.<br>+                   <br>+                     ((FileDirectory forFileName: fileName) fileExists: fileName)<br>+                                 ifFalse: [ "If the default file name, which is derived from the current window title, does not exist, ask the user once to confirm the location."             <br>+                                     self<br>+                                                 saveContents: stringToSave<br>+                                           accessMode: #create]<br>+                                 ifTrue: [ "Update/replace the contents in the existing file."<br>+                                      self<br>+                                                 saveContents: stringToSave<br>+                                           onFileNamed: fileName<br>+                                                accessMode: #update]] ]).!<br><br>Item was added:<br>+ ----- Method: Workspace>>saveContentsInFileOnAcceptEnabled (in category 'menu commands') -----<br>+ saveContentsInFileOnAcceptEnabled<br>+ <br>+       ^ (acceptAction notNil and: [acceptAction home selector = #saveContentsInFileOnAccept])!<br><br>Item was added:<br>+ ----- Method: Workspace>>saveContentsInFileOnAcceptWording (in category 'menu commands') -----<br>+ saveContentsInFileOnAcceptWording<br>+ <br>+         ^ (self saveContentsInFileOnAcceptEnabled<br>+            ifTrue: ['<yes> save contents to {1} on accept']<br>+               ifFalse: ['<no> save contents to {1} on accept']) translated<br>+                   format: { self suggestedFileNameForSave contractTo: 32 }!<br><br>Item was added:<br>+ ----- Method: Workspace>>setFileName: (in category 'initialize-release') -----<br>+ setFileName: fileName<br>+ <br>+    | directory |<br>+        directory := FileDirectory forFileName: fileName.<br>+    self fileDirectory: directory.<br>+       self windowTitle: (directory localNameFor: fileName).!<br><br>Item was added:<br>+ ----- Method: Workspace>>windowReqNewLabel: (in category 'user edits') -----<br>+ windowReqNewLabel: newLabel<br>+       "The user has edited the window label. Remember for a later save-to-file request. See #defaultFileNameForSave."<br>+    <br>+     self setProperty: #windowTitle toValue: newLabel.<br>+    ^ true!<br><br>Item was added:<br>+ ----- Method: Workspace>>windowTitle (in category 'accessing') -----<br>+ windowTitle<br>+ <br>+  ^ self valueOfProperty: #windowTitle ifAbsent: ['Workspace']!<br><br>Item was added:<br>+ ----- Method: Workspace>>windowTitle: (in category 'accessing') -----<br>+ windowTitle: aString<br>+      "Normalize window title to not expose file extension in regular workspaces."<br>+       <br>+     | normalizedTitle |<br>+  normalizedTitle := ((aString includesSubstring: 'Workspace') and: [aString endsWithAnyOf: #('.text' '.txt')])<br>+                ifTrue: [aString copyFrom: 1 to: (aString lastIndexOf: $.) - 1]<br>+              ifFalse: [aString].<br>+ <br>+      self setProperty: #windowTitle toValue: normalizedTitle.<br>+     self changed: #windowTitle.!<br><br>Item was changed:<br>  (PackageInfo named: 'Tools') postscript: 'ChangeSorter allSubInstancesDo: [:sorter |<br>     (sorter instVarNamed: ''contentsAreStyleable'') ifNil: [<br>+             sorter instVarNamed: ''contentsAreStyleable'' put: true]].<br>+ <br>+ "Convert existing properties from Morphic windows to the model. For MVC compatibility."<br>+ Workspace allInstancesDo: [:workspace | <br>+      workspace containingWindow ifNotNil: [:window |<br>+              (window valueOfProperty: #myDir) ifNotNil: [:directory |<br>+                     workspace setProperty: #fileDirectory toValue: directory].<br>+           (window valueOfProperty: #lineConversion) ifNotNil: [:symbol |<br>+                       workspace setProperty: #fileLineConversion toValue: symbol].<br>+                 workspace setProperty: #windowTitle toValue: window label].<br>+  (workspace acceptAction notNil and: [workspace acceptAction home selector = #open])<br>+          ifTrue: [workspace appendContentsToFileOnAccept]].'!<br>-                 sorter instVarNamed: ''contentsAreStyleable'' put: true]].'!<br><br><br></no></yes></no></yes></div></blockquote></div>