<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>