ToolApplicationModel subclass:#NewChangesBrowser
instanceVariableNames:'changes changeFileName skipSignal compareChanges
changeFileTimestamp modified autoUpdateBlock editingClassSource
filterCompletionBlock'
classVariableNames:'AutoUpdate CompressSnapshotInfo CategoryColumn DeltaInfoColumn
TypeColumn TimeStampColumn PositionColumn'
poolDictionaries:''
category:'Interface-Browsers'
!
Object subclass:#Change
instanceVariableNames:'delta string type timeStamp category chunk position className
followUp'
classVariableNames:''
poolDictionaries:''
privateIn:NewChangesBrowser
!
!NewChangesBrowser class methodsFor:'documentation'!
documentation
"
The future Changes Browser.
[start with:]
NewChangesBrowser open
NewChangesBrowser openOnFile:aFileName
[author:]
Thomas Zwick, eXept Software AG
"
! !
!NewChangesBrowser class methodsFor:'instance creation'!
openOnFile:aFileName
"create c changes browser on a change file"
^ ((self new label:(self label , ': ', aFileName))
changeFileName:aFileName) open
! !
!NewChangesBrowser class methodsFor:'accessing'!
autoSelectNext
"returning true here, makes a Delete operation automatically
select the next change"
^ true
!
label
^'Changes Browser'
! !
!NewChangesBrowser class methodsFor:'help specs'!
helpSpec
"This resource specification was automatically generated
by the UIHelpTool of ST/X."
"Do not manually edit this!! If it is corrupted,
the UIHelpTool may not be able to read the specification."
"
UIHelpTool openOnClass:NewChangesBrowser
"
<resource: #help>
^super helpSpec addPairsFrom:#(
#applyAll
'Apply all changes from the first one to the last made change.'
#applyForClassToEnd
'Apply all changes from the selected one up to the last made change affecting on the same class as the class of the selected change.'
#applyFromLastSnapshot
'Apply all changes from the last snapshot up to the last made change.'
#applyLine
'Apply the selected change.'
#applyToEnd
'Apply all changes from the selected one up to the last made change.'
#deleteCompress
'Deletes all multiple changes but leaving the last one.'
#deleteCompressForClass
'Deletes all multiple changes affecting on the same class as the class of the selected change but leaving the last one.'
#deleteForClassToEnd
'Deletes all changes from the selected one up to the last made change affecting on the same class as the class of the selected change.'
#deleteLine
'Deletes the selected change from the list of changes.'
#deleteToEnd
'Deletes all changes from the selected one up to the last made change.'
#fileLoadFrom
'Opens a dialog for selecting and loading another changes files.'
#fileReload
'Reloads the changes file (undo your modifications).'
#fileSave
'Saves the list of changes into current changes file name.'
#settingsAutoUpdate
'Turns on/off whether the Changes Browser makes automatically an update after a change done while running.'
#settingsColumns
'Turns on/off whether columns are shown by the table.'
#settingsColumnsCategory
'Turns on/off whether a column for the category of the change is shown by the table.'
#settingsColumnsDeltaInfo
'Turns on/off whether a column for the delta info of the change is shown by the table.'
#settingsColumnsPosition
'Turns on/off whether a column for the position of the change in the change file is shown by the table.'
#settingsColumnsTimeStamp
'Turns on/off whether a column for the time stamp of the change is shown by the table.'
#settingsColumnsType
'Turns on/off whether a column for the type of the change is shown by the table.'
#testCompareWithCurrentVersion
'Opens a info dialog showing the compare results between the method code of the selected change with the current code.'
#testFindLastSnapshot
'Moves upwards the list of changes in order to find to the last made snapshot.'
#testFindNextSnapshot
'Moves downwards the list of changes in order to find to the next made snapshot.'
)
! !
!NewChangesBrowser class methodsFor:'image specs'!
applyFromLastSnapshotIcon
"This resource specification was automatically generated
by the ImageEditor of ST/X."
"Do not manually edit this!! If it is corrupted,
the ImageEditor may not be able to read the specification."
"
ImageEditor openOnClass:self andSelector:#applyFromLastSnapshotIcon
"
<resource: #image>
^Icon
constantNamed:#'NewChangesBrowser applyFromLastSnapshotIcon'
ifAbsentPut:[(Depth4Image new) width: 22; height: 22; photometric:(#palette); bitsPerSample:(#(4 )); samplesPerPixel:(1); bits:(ByteArray fromPackedString:'@@@@@@@@@@@QDQ<@@@@@@@@@@AH"H@@@@@@@@@@@D"H @@@@@@@@@@@RH"@@@@@@@@@@@AH"H@@@@@@@@@@@@@@@@@@@@@@@@@<@@@@@@@@@@@@@@@@@@@@@@@;.;.; @@@@@@@@C.;.;.@@@@@@@@@@@@@@8@@N@@@@C?????C @@@N@@@O????<@@@8@@@@@@@@@C0@@@@8@@O????<O@@C @@@@?????0@@@@C @@@@@@@O@@@N@@@@DQDQDP<@C @@@@@_??DQ@@@@@@@@@A????D@@@@@@@@@DQDQDP@@@@C @@@@@@@@@@@@@@@@@b') ; colorMapFromArray:#[0 0 0 255 255 255 255 0 0 0 255 0 0 0 255 0 255 255 255 255 0 255 0 255 127 0 0 0 127 0 0 0 127 0 127 127 127 127 0 127 0 127 127 127 127 170 170 170]; mask:((Depth1Image new) width: 22; height: 22; photometric:(#blackIs0); bitsPerSample:(#(1 )); samplesPerPixel:(1); bits:(ByteArray fromPackedString:'@@C<@@C<@@C<@@C<@@C<@@A<@@D@@?<@@?<@@?<LC?<\C?<\C?<8O?08O?10O?10??C ??[@??_@?<^@?<_@?<_@') ; yourself); yourself]!
applyIcon
"This resource specification was automatically generated
by the ImageEditor of ST/X."
"Do not manually edit this!! If it is corrupted,
the ImageEditor may not be able to read the specification."
"
ImageEditor openOnClass:self andSelector:#applyIcon
"
<resource: #image>
^Icon
constantNamed:#'NewChangesBrowser applyIcon'
ifAbsentPut:[(Depth2Image new) width: 22; height: 22; photometric:(#palette); bitsPerSample:(#(2 )); samplesPerPixel:(1); bits:(ByteArray fromPackedString:'UUUUUUUPUUUUUUUPUUUUUUU[UUUUUUUPP@@@@@@QQUUUUUTPQUUUUUTVQ**UUUTPQUUUUUTPQUUUUUTXQ***UUTPQ**UUUTPQ*****TPQ***%UTPQ****)TPQUUUUUTPQUUUUUTUP@@@@@@PUUUUUUUPUUUUUUUPUUUUUUUPUUUUUUUP') ; colorMapFromArray:#[0 0 0 255 255 255 170 170 170 255 0 0]; mask:((Depth1Image new) width: 22; height: 22; photometric:(#blackIs0); bitsPerSample:(#(1 )); samplesPerPixel:(1); bits:(ByteArray fromPackedString:'@@@@@@@@@@@@@@@@_??8_??8_??8_??8_??8_??8_??8_??8_??8_??8_??8_??8_??8_??8@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@a') ; yourself); yourself]!
applyToEndIcon
"This resource specification was automatically generated
by the ImageEditor of ST/X."
"Do not manually edit this!! If it is corrupted,
the ImageEditor may not be able to read the specification."
"
ImageEditor openOnClass:self andSelector:#applyToEndIcon
"
<resource: #image>
^Icon
constantNamed:#'NewChangesBrowser applyToEndIcon'
ifAbsentPut:[(Depth2Image new) width: 22; height: 22; photometric:(#palette); bitsPerSample:(#(2 )); samplesPerPixel:(1); bits:(ByteArray fromPackedString:'@@@@@@@@@@@@@@@@@@@B** H@@@B** H@@@@@@ @@@@**( @@@@**(@J@@@@@H@@@@O??H@@@@O??@@@@@@@C@H@@C??3@@ @C??0@ @@@@@0@B@@UUT0B@K@UUT@@H@@@@D@H@CEUUDB@@JG?U@@@@MG?=@@@@@EUU@@B@@@@@@@@@H') ; colorMapFromArray:#[0 0 0 255 255 255 127 127 127 170 170 170]; mask:((Depth1Image new) width: 22; height: 22; photometric:(#blackIs0); bitsPerSample:(#(1 )); samplesPerPixel:(1); bits:(ByteArray fromPackedString:'@@@@@C?0@C?0@C?0@O?0@O?0@O?0@??@@??@@??LC?<\C?<\C?<8O?08O?10O?10??C ??[@??_@?<^@?<_@?<_@') ; yourself); yourself]!
compressIcon
"This resource specification was automatically generated
by the ImageEditor of ST/X."
"Do not manually edit this!! If it is corrupted,
the ImageEditor may not be able to read the specification."
"
ImageEditor openOnClass:self andSelector:#compressIcon
"
<resource: #image>
^Icon
constantNamed:#'NewChangesBrowser compressIcon'
ifAbsentPut:[(Depth2Image new) width: 22; height: 22; photometric:(#palette); bitsPerSample:(#(2 )); samplesPerPixel:(1); bits:(ByteArray fromPackedString:'@@@@@@@@@@????<@@@:***(@@@8@@@8@@@8@@@8@@@@@@@8@@@@@@@8A@@@@@@8@@:**0@8HC**+@@8@@@@@@@8H@@@@@@8@@@@@@@8@@@@@@@8@@@@@@@8@@C????8@@C****(@@@@@@@@H@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@') ; colorMapFromArray:#[0 0 0 124 124 124 170 170 170 255 255 255]; mask:((Depth1Image new) width: 22; height: 22; photometric:(#blackIs0); bitsPerSample:(#(1 )); samplesPerPixel:(1); bits:(ByteArray fromPackedString:'@??0A??8A??8A??8A8A8G>A8@@A8G?98O?18_?!!8??A8@@A8G>A8@0A8C??8C??8C??8C??0@0@@D2@@G>@@D2@@') ; yourself); yourself]!
deleteIcon
"This resource specification was automatically generated
by the ImageEditor of ST/X."
"Do not manually edit this!! If it is corrupted,
the ImageEditor may not be able to read the specification."
"
ImageEditor openOnClass:self andSelector:#deleteIcon
"
<resource: #image>
^Icon
constantNamed:#'NewChangesBrowser deleteIcon'
ifAbsentPut:[(Depth2Image new) width: 22; height: 22; photometric:(#palette); bitsPerSample:(#(2 )); samplesPerPixel:(1); bits:(ByteArray fromPackedString:'@@@@@@@@AUUUU@@OA??UU@@@AUUUU@@@AUUUU@B@A???U@H@A??5U@@@A???0@@@AUUUB@B@@@@@@@@@@@@@A@BK@@@@A_:@@@@@AO2G@@@@AL2@@@@@AL2C@@@@AL2@@@@@AL2@@@@@AL2H@@@@AL2@@@@@A\:@@@@@H_8 @@@@@@@H') ; colorMapFromArray:#[0 0 0 255 255 255 127 127 127 170 170 170]; mask:((Depth1Image new) width: 22; height: 22; photometric:(#blackIs0); bitsPerSample:(#(1 )); samplesPerPixel:(1); bits:(ByteArray fromPackedString:'_?<@_?<@_?<P_?<P_?<X_?<X_?<H_? H_?O<_?XL@@_<@@_<@@_<@@_<@@_<@@_<@@_<@@_<@@_<@@_<@@_<@@G0') ; yourself); yourself]!
deleteToEndIcon
"This resource specification was automatically generated
by the ImageEditor of ST/X."
"Do not manually edit this!! If it is corrupted,
the ImageEditor may not be able to read the specification."
"
ImageEditor openOnClass:self andSelector:#deleteToEndIcon
"
<resource: #image>
^Icon
constantNamed:#'NewChangesBrowser deleteToEndIcon'
ifAbsentPut:[(Depth2Image new) width: 22; height: 22; photometric:(#palette); bitsPerSample:(#(2 )); samplesPerPixel:(1); bits:(ByteArray fromPackedString:'@@@@@@@@@B** @@O@B** @@@@@@@ @@@@??< @B@@??<@@H@@@@L@@@@EUUL@@@@G?U@B@B@G?=@@@@@EUU@A@BK@@@@A_:@@@@@AO2G@@@@AL2@@@@@AL2C@@@@AL2@@@@@AL2@@@@@AL2H@@@@AL2@@@@@A\:@@@@@H_8 @@@@@@@H') ; colorMapFromArray:#[0 0 0 255 255 255 127 127 127 170 170 170]; mask:((Depth1Image new) width: 22; height: 22; photometric:(#blackIs0); bitsPerSample:(#(1 )); samplesPerPixel:(1); bits:(ByteArray fromPackedString:'C?0@C?0@C?0PO?0PO?0XO?0X??@H??@H??O<?<XL?<_<?<_<@@_<@@_<@@_<@@_<@@_<@@_<@@_<@@_<@@_<@@G0') ; yourself); yourself]!
findLastSnapshotIcon
"This resource specification was automatically generated
by the ImageEditor of ST/X."
"Do not manually edit this!! If it is corrupted,
the ImageEditor may not be able to read the specification."
"
ImageEditor openOnClass:self andSelector:#findLastSnapshotIcon
"
<resource: #image>
^Icon
constantNamed:#'NewChangesBrowser findLastSnapshotIcon'
ifAbsentPut:[(Depth2Image new) width: 22; height: 22; photometric:(#palette); bitsPerSample:(#(2 )); samplesPerPixel:(1); bits:(ByteArray fromPackedString:'@EUUUW@A@F***(@@@F***(@@@F***(@@@F***(@@@F***(@@@F***(@@@F***(@A@F***(@M@F***(@H@F***(@@@F***(@@@F***(@I@L@@@@@@@@@@@@@A@P@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@') ; colorMapFromArray:#[0 0 0 255 255 255 255 0 0 170 170 170]; mask:((Depth1Image new) width: 22; height: 22; photometric:(#blackIs0); bitsPerSample:(#(1 )); samplesPerPixel:(1); bits:(ByteArray fromPackedString:'C??@C??@C??@C??@C??@C??@C??@C??@C??@C??@C??@C??@C??@C??B@@@B@C@B@G B@O0@@_8@@G @@G @@G @') ; yourself); yourself]!
findNextSnapshotIcon
"This resource specification was automatically generated
by the ImageEditor of ST/X."
"Do not manually edit this!! If it is corrupted,
the ImageEditor may not be able to read the specification."
"
ImageEditor openOnClass:self andSelector:#findNextSnapshotIcon
"
<resource: #image>
^Icon
constantNamed:#'NewChangesBrowser findNextSnapshotIcon'
ifAbsentPut:[(Depth2Image new) width: 22; height: 22; photometric:(#palette); bitsPerSample:(#(2 )); samplesPerPixel:(1); bits:(ByteArray fromPackedString:'@@@@@DA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@D@B@@@@@@@@@@@@@@@E@@@@@@@@@EUUUW@N@F***(@@@F***(@@@F***(@@@F***(@@@F***(@@@F***(@@@V***(@H@F***(@@@F***(@@@F***(@@@F***(@@@F***(@@@L@@@@@@') ; colorMapFromArray:#[0 0 0 255 255 255 255 0 0 170 170 170]; mask:((Depth1Image new) width: 22; height: 22; photometric:(#blackIs0); bitsPerSample:(#(1 )); samplesPerPixel:(1); bits:(ByteArray fromPackedString:'@G @@G @@G @@_8@@O0@@G @@C@@@@@@C??@C??@C??@C??@C??@C??BC??BC??BC??BC??@C??@C??@C??@C??@') ; yourself); yourself]! !
!NewChangesBrowser class methodsFor:'interface specs'!
windowSpec
"This resource specification was automatically generated
by the UIPainter of ST/X."
"Do not manually edit this!! If it is corrupted,
the UIPainter may not be able to read the specification."
"
UIPainter new openOnClass:NewChangesBrowser andSelector:#windowSpec
NewChangesBrowser new openInterface:#windowSpec
NewChangesBrowser open
"
<resource: #canvas>
^
#(#FullSpec
#window:
#(#WindowSpec
#name: ''
#layout: #(#LayoutFrame 329 0 181 0 828 0 580 0)
#label: ''
#min: #(#Point 10 10)
#max: #(#Point 1152 900)
#bounds: #(#Rectangle 329 181 829 581)
#menu: #menu
#usePreferredExtent: false
)
#component:
#(#SpecCollection
#collection:
#(
#(#MenuPanelSpec
#name: 'menuToolbarView'
#layout: #(#LayoutFrame 0 0.0 0 0 0 1.0 32 0)
#menu: #menuToolbar
#style: #(#FontDescription #helvetica #medium #roman 10)
#showSeparatingLines: true
)
#(#VariableVerticalPanelSpec
#name: 'variableVerticalPanel1'
#layout: #(#LayoutFrame 0 0.0 34 0 0 1.0 -26 1.0)
#component:
#(#SpecCollection
#collection:
#(
#(#ViewSpec
#name: 'Box1'
#component:
#(#SpecCollection
#collection:
#(
#(#DataSetSpec
#name: 'changesDataSetView'
#layout: #(#LayoutFrame 0 0.0 0 0.0 0 1.0 -28 1.0)
#model: #selectionOfChange
#menu: #menuTable
#hasHorizontalScrollBar: true
#hasVerticalScrollBar: true
#miniScrollerHorizontal: true
#dataList: #listOfChanges
#useIndex: false
#has3Dsepartors: true
#doubleClickSelector: #doBrowseClass
#columnHolder: #listOfChangeColumns
#valueChangeSelector: #changeSelected:
)
#(#ViewSpec
#name: 'Box2'
#layout: #(#LayoutFrame 0 0.0 -28 1 0 1.0 0 1.0)
#component:
#(#SpecCollection
#collection:
#(
#(#LabelSpec
#name: 'filterLabel'
#layout: #(#AlignmentOrigin 37 0 13 0.0 1 0.5)
#label: 'Filter:'
#style: #(#FontDescription #helvetica #medium #roman 10)
#adjust: #left
)
#(#InputFieldSpec
#name: 'filterField'
#layout: #(#LayoutFrame 41 0.0 3 0 285 0 25 0)
#model: #valueOfFilter
#immediateAccept: false
#acceptOnReturn: false
)
#(#ProgressIndicatorSpec
#name: 'readProgressIndicator'
#layout: #(#LayoutFrame 41 0 3 0 285 0 25 0)
#model: #valueOfReadProgress
#foregroundColor: #(#Color 0.0 60.0 60.0)
)
#(#LabelSpec
#name: 'Label3'
#layout: #(#LayoutFrame 297 0.0 2 0.0 307 0 13 0)
#label: ' '
#backgroundColor: #(#Color 100.0 100.0 100.0)
#level: 1
)
#(#LabelSpec
#name: 'Label6'
#layout: #(#LayoutFrame 311 0 0 0.0 396 0 14 0)
#label: '= method change'
#style: #(#FontDescription #helvetica #medium #roman 10)
#adjust: #left
)
#(#LabelSpec
#name: 'Label9'
#layout: #(#LayoutFrame 403 0.0 1 0.0 413 0 12 0)
#label: ' '
#backgroundColor: #(#Color 49.9992 49.9992 49.9992)
#level: 1
)
#(#LabelSpec
#name: 'Label10'
#layout: #(#LayoutFrame 418 0 0 0.0 496 0 14 0)
#label: '= class change'
#style: #(#FontDescription #helvetica #medium #roman 10)
#adjust: #left
)
#(#LabelSpec
#name: 'Label1'
#layout: #(#LayoutFrame 297 0.0 16 0.0 307 0 27 0)
#label: ' '
#backgroundColor: #(#Color 100.0 0.0 0.0)
#level: 1
)
#(#LabelSpec
#name: 'Label2'
#layout: #(#LayoutFrame 312 0 14 0.0 376 0 28 0)
#label: '= snapshot'
#style: #(#FontDescription #helvetica #medium #roman 10)
#adjust: #left
)
#(#LabelSpec
#name: 'Label5'
#layout: #(#LayoutFrame 372 0.0 16 0.0 382 0 27 0)
#label: ' '
#backgroundColor: #(#Color 0.0 80.0 80.0)
#level: 1
)
#(#LabelSpec
#name: 'Label4'
#layout: #(#LayoutFrame 387 0 14 0.0 435 0 28 0)
#label: '= file in'
#style: #(#FontDescription #helvetica #medium #roman 10)
#adjust: #left
)
#(#LabelSpec
#name: 'Label7'
#layout: #(#LayoutFrame 428 0.0 16 0.0 438 0 27 0)
#label: ' '
#backgroundColor: #(#Color 0.0 0.0 100.0)
#level: 1
)
#(#LabelSpec
#name: 'Label8'
#layout: #(#LayoutFrame 443 0 14 0.0 504 0 28 0)
#label: '= check in'
#style: #(#FontDescription #helvetica #medium #roman 10)
#adjust: #left
)
)
)
)
)
)
)
#(#TextEditorSpec
#name: 'changeTextEditor'
#model: #valueOfChangeText
#hasHorizontalScrollBar: true
#hasVerticalScrollBar: true
#miniScrollerHorizontal: true
#isReadOnly: true
)
)
)
#handles: #(#Any 0.5 1.0)
)
#(#UISubSpecification
#name: 'windowSpecForInfoBar'
#layout: #(#LayoutFrame 0 0.0 -24 1 0 1.0 0 1.0)
#majorKey: #ToolApplicationModel
#minorKey: #windowSpecForInfoBar
)
)
)
)
! !
!NewChangesBrowser class methodsFor:'list specs'!
specOfChangeColumns
^
#(
#(#DataSetColumnSpec
#rendererType: #rowSelector
#backgroundSelector: #listColor
)
#(#DataSetColumnSpec
#label: 'Change'
#labelAlignment: #left
#model: #string
#canSelect: false
)
#(#DataSetColumnSpec
#label: 'Category'
#labelAlignment: #left
#model: #category
#canSelect: false
)
#(#DataSetColumnSpec
#label: 'Delta Info'
#labelAlignment: #left
#model: #delta
#canSelect: false
)
#(#DataSetColumnSpec
#label: 'Time Stamp'
#labelAlignment: #left
#model: #timeStamp
#canSelect: false
)
#(#DataSetColumnSpec
#label: 'Type'
#labelAlignment: #left
#model: #type
#canSelect: false
)
#(#DataSetColumnSpec
#label: 'Position'
#labelAlignment: #left
#model: #position
#canSelect: false
)
)
! !
!NewChangesBrowser class methodsFor:'menu specs'!
menu
"This resource specification was automatically generated
by the MenuEditor of ST/X."
"Do not manually edit this!! If it is corrupted,
the MenuEditor may not be able to read the specification."
"
MenuEditor new openOnClass:NewChangesBrowser andSelector:#menu
(Menu new fromLiteralArrayEncoding:(NewChangesBrowser menu)) startUp
"
<resource: #menu>
^
#(#Menu
#(
#(#MenuItem
#label: 'About'
#activeHelpKey: #about
#labelImage: #(#ResourceRetriever nil #menuIcon)
#submenuChannel: #menuAbout
)
#(#MenuItem
#label: 'File'
#activeHelpKey: #file
#submenu:
#(#Menu
#(
#(#MenuItem
#label: 'Reload'
#value: #doReload
#activeHelpKey: #fileReload
#enabled: #valueOfNotReading
)
#(#MenuItem
#label: '-'
)
#(#MenuItem
#label: 'Load From...'
#value: #doLoadFrom
#activeHelpKey: #fileLoadFrom
#enabled: #valueOfNotReading
)
#(#MenuItem
#label: '-'
)
#(#MenuItem
#label: 'Save'
#value: #doSave
#activeHelpKey: #fileSave
#enabled: #valueOfNotReading
)
#(#MenuItem
#label: '-'
)
#(#MenuItem
#label: 'Browse Class'
#value: #doBrowseClass
#activeHelpKey: #fileBrowseClass
#enabled: #valueOfHavingChangeSelection
)
#(#MenuItem
#label: '-'
)
#(#MenuItem
#label: 'Exit'
#value: #closeRequest
#activeHelpKey: #fileExit
#enabled: #valueOfNotReading
)
) nil
nil
)
)
#(#MenuItem
#label: 'Apply'
#submenu:
#(#Menu
#(
#(#MenuItem
#label: 'Line'
#value: #doApply
#activeHelpKey: #applyLine
#enabled: #valueOfHavingChangeSelection
)
#(#MenuItem
#label: 'To End'
#value: #doApplyToEnd
#activeHelpKey: #applyToEnd
#enabled: #valueOfHavingSelection
)
#(#MenuItem
#label: 'For Class To End'
#value: #doApplyForClassToEnd
#activeHelpKey: #applyForClassToEnd
#enabled: #valueOfHavingChangeSelection
)
#(#MenuItem
#label: 'All'
#value: #doApplyAll
#activeHelpKey: #applyAll
#enabled: #valueOfNotReading
)
#(#MenuItem
#label: '-'
)
#(#MenuItem
#label: 'From Last Snapshot'
#value: #doApplyFromLastSnapshot
#activeHelpKey: #applyFromLastSnapshot
#enabled: #valueOfNotReading
)
) nil
nil
)
)
#(#MenuItem
#label: 'Delete'
#activeHelpKey: #edit
#submenu:
#(#Menu
#(
#(#MenuItem
#label: 'Line'
#value: #doDelete
#activeHelpKey: #deleteLine
#enabled: #valueOfHavingSelection
)
#(#MenuItem
#label: 'To End'
#value: #doDeleteToEnd
#activeHelpKey: #deleteToEnd
#enabled: #valueOfHavingSelection
)
#(#MenuItem
#label: 'For Class To End'
#value: #doDeleteForClassToEnd
#activeHelpKey: #deleteForClassToEnd
#enabled: #valueOfHavingChangeSelection
)
#(#MenuItem
#label: '-'
)
#(#MenuItem
#label: 'Compress'
#value: #doCompress
#activeHelpKey: #deleteCompress
#enabled: #valueOfNotReading
)
#(#MenuItem
#label: 'Compress For Class'
#value: #doCompressForClass
#activeHelpKey: #deleteCompressForClass
#enabled: #valueOfHavingChangeSelection
)
) nil
nil
)
)
#(#MenuItem
#label: 'Test'
#activeHelpKey: #test
#submenu:
#(#Menu
#(
#(#MenuItem
#label: 'Find Last Snapshot'
#value: #doFindSnapshot:
#activeHelpKey: #testFindLastSnapshot
#enabled: #valueOfHavingSelection
#argument: 'last'
)
#(#MenuItem
#label: 'Find Next Snapshot'
#value: #doFindSnapshot:
#activeHelpKey: #testFindNextSnapshot
#enabled: #valueOfHavingSelection
#argument: 'next'
)
#(#MenuItem
#label: '-'
)
#(#MenuItem
#label: 'Compare With Current Version'
#value: #doCompare
#activeHelpKey: #testCompareWithCurrentVersion
#enabled: #valueOfHavingChangeSelection
)
) nil
nil
)
)
#(#MenuItem
#label: 'Settings'
#activeHelpKey: #settings
#submenu:
#(#Menu
#(
#(#MenuItem
#label: 'Auto Update'
#activeHelpKey: #settingsAutoUpdate
#enabled: #valueOfNotReading
#indication: #autoUpdateMode:
)
#(#MenuItem
#label: '-'
)
#(#MenuItem
#label: 'Columns'
#activeHelpKey: #settingsColumns
#submenu:
#(#Menu
#(
#(#MenuItem
#label: 'Category'
#activeHelpKey: #settingsColumnsCategory
#indication: #categoryColumn:
)
#(#MenuItem
#label: 'Delta Info'
#activeHelpKey: #settingsColumnsDeltaInfo
#indication: #deltaInfoColumn:
)
#(#MenuItem
#label: 'Type'
#activeHelpKey: #settingsColumnsType
#indication: #typeColumn:
)
#(#MenuItem
#label: 'Time Stamp'
#activeHelpKey: #settingsColumnsTimeStamp
#indication: #timeStampColumn:
)
#(#MenuItem
#label: 'Position'
#activeHelpKey: #settingsColumnsPosition
#indication: #positionColumn:
)
) nil
nil
)
)
#(#MenuItem
#label: '-'
)
#(#MenuItem
#label: 'Fonts'
#enabled: #valueOfNotReading
#submenuChannel: #menuFont
)
) nil
nil
)
)
#(#MenuItem
#label: 'Help'
#startGroup: #right
#activeHelpKey: #help
#submenuChannel: #menuHelp
)
) nil
nil
)
!
menuTable
"This resource specification was automatically generated
by the MenuEditor of ST/X."
"Do not manually edit this!! If it is corrupted,
the MenuEditor may not be able to read the specification."
"
MenuEditor new openOnClass:NewChangesBrowser andSelector:#menuTable
(Menu new fromLiteralArrayEncoding:(NewChangesBrowser menuTable)) startUp
"
<resource: #menu>
^
#(#Menu
#(
#(#MenuItem
#label: 'Apply'
#value: #doApply
#activeHelpKey: #applyLine
#enabled: #valueOfHavingChangeSelection
)
#(#MenuItem
#label: 'Apply To End'
#value: #doApplyToEnd
#activeHelpKey: #applyToEnd
#enabled: #valueOfHavingSelection
)
#(#MenuItem
#label: 'Apply For Class To End'
#value: #doApplyForClassToEnd
#activeHelpKey: #applyForClassToEnd
#enabled: #valueOfHavingChangeSelection
)
#(#MenuItem
#label: '-'
)
#(#MenuItem
#label: 'Delete'
#value: #doDelete
#activeHelpKey: #deleteLine
#enabled: #valueOfHavingSelection
)
#(#MenuItem
#label: 'Delete To End'
#value: #doDeleteToEnd
#activeHelpKey: #deleteToEnd
#enabled: #valueOfHavingSelection
)
#(#MenuItem
#label: 'Delete For Class To End'
#value: #doDeleteForClassToEnd
#activeHelpKey: #deleteForClassToEnd
#enabled: #valueOfHavingChangeSelection
)
#(#MenuItem
#label: '-'
)
#(#MenuItem
#label: 'Compress For Class'
#value: #doCompressForClass
#activeHelpKey: #deleteCompressForClass
#enabled: #valueOfHavingChangeSelection
)
) nil
nil
)
!
menuToolbar
"This resource specification was automatically generated
by the MenuEditor of ST/X."
"Do not manually edit this!! If it is corrupted,
the MenuEditor may not be able to read the specification."
"
MenuEditor new openOnClass:NewChangesBrowser andSelector:#menuToolbar
(Menu new fromLiteralArrayEncoding:(NewChangesBrowser menuToolbar)) startUp
"
<resource: #menu>
^
#(#Menu
#(
#(#MenuItem
#label: 'Load From'
#isButton: true
#value: #doLoadFrom
#activeHelpKey: #fileLoadFrom
#enabled: #valueOfNotReading
#labelImage: #(#ResourceRetriever nil #loadIcon)
)
#(#MenuItem
#label: 'Save'
#isButton: true
#value: #doSave
#activeHelpKey: #fileSave
#enabled: #valueOfNotReading
#labelImage: #(#ResourceRetriever nil #saveIcon)
)
#(#MenuItem
#label: ''
)
#(#MenuItem
#label: 'Compress'
#isButton: true
#value: #doCompress
#activeHelpKey: #deleteCompress
#enabled: #valueOfNotReading
#labelImage: #(#ResourceRetriever nil #compressIcon)
)
#(#MenuItem
#label: ''
)
#(#MenuItem
#label: 'Apply'
#isButton: true
#value: #doApply
#activeHelpKey: #applyLine
#enabled: #valueOfHavingChangeSelection
#labelImage: #(#ResourceRetriever nil #applyIcon)
)
#(#MenuItem
#label: 'Apply To End'
#isButton: true
#value: #doApplyToEnd
#activeHelpKey: #applyToEnd
#enabled: #valueOfHavingSelection
#labelImage: #(#ResourceRetriever nil #applyToEndIcon)
)
#(#MenuItem
#label: 'Apply From Last Snapshot'
#isButton: true
#value: #doApplyFromLastSnapshot
#activeHelpKey: #applyFromLastSnapshot
#enabled: #valueOfNotReading
#labelImage: #(#ResourceRetriever nil #applyFromLastSnapshotIcon)
)
#(#MenuItem
#label: ''
)
#(#MenuItem
#label: 'Delete'
#isButton: true
#value: #doDelete
#activeHelpKey: #deleteLine
#enabled: #valueOfHavingSelection
#labelImage: #(#ResourceRetriever nil #deleteIcon)
)
#(#MenuItem
#label: 'Delete To End'
#isButton: true
#value: #doDeleteToEnd
#activeHelpKey: #deleteToEnd
#enabled: #valueOfHavingSelection
#labelImage: #(#ResourceRetriever nil #deleteToEndIcon)
)
#(#MenuItem
#label: ''
)
#(#MenuItem
#label: 'Find Last Snapshot'
#isButton: true
#value: #doFindSnapshot:
#activeHelpKey: #testFindLastSnapshot
#enabled: #valueOfHavingSelection
#argument: 'last'
#labelImage: #(#ResourceRetriever nil #findLastSnapshotIcon)
)
#(#MenuItem
#label: 'Find Next Snapshot'
#isButton: true
#value: #doFindSnapshot:
#activeHelpKey: #testFindNextSnapshot
#enabled: #valueOfHavingSelection
#argument: 'next'
#labelImage: #(#ResourceRetriever nil #findNextSnapshotIcon)
)
) nil
nil
)
! !
!NewChangesBrowser methodsFor:'accesssing - columns'!
categoryColumn
^CategoryColumn ? (CategoryColumn := true)
!
categoryColumn: aBoolean
self changeColumn: 'Category' add: (CategoryColumn := aBoolean)
!
deltaInfoColumn
^DeltaInfoColumn ? (DeltaInfoColumn := false)
!
deltaInfoColumn: aBoolean
self changeColumn: 'Delta Info' add: (DeltaInfoColumn := aBoolean)
!
positionColumn
^PositionColumn ? (PositionColumn := true)
!
positionColumn: aBoolean
self changeColumn: 'Position' add: (PositionColumn := aBoolean)
!
timeStampColumn
^TimeStampColumn ? (TimeStampColumn := false)
!
timeStampColumn: aBoolean
self changeColumn: 'Time Stamp' add: (TimeStampColumn := aBoolean)
!
typeColumn
^TypeColumn ? (TypeColumn := false)
!
typeColumn: aBoolean
self changeColumn: 'Type' add: (TypeColumn := aBoolean)
! !
!NewChangesBrowser methodsFor:'accesssing - views'!
filterField
^builder componentAt: #filterField
!
filterLabel
^builder componentAt: #filterLabel
!
readProgressIndicator
^builder componentAt: #readProgressIndicator
! !
!NewChangesBrowser methodsFor:'aspects'!
listOfChangeColumns
|holder|
(holder := builder bindingAt:#listOfChangeColumns) isNil ifTrue:[
builder aspectAt:#listOfChangeColumns put:(holder := List new).
self changeColumn: nil add: true.
self changeColumn: 'Change' add: true.
self categoryColumn: self categoryColumn.
self timeStampColumn: self timeStampColumn.
self typeColumn: self typeColumn.
self deltaInfoColumn: self deltaInfoColumn.
self positionColumn: self positionColumn.
].
^ holder
!
listOfChanges
|holder|
(holder := builder bindingAt:#listOfChanges) isNil ifTrue:[
builder aspectAt:#listOfChanges put:(holder := List new)
].
^ holder
!
selectionOfChange
|holder|
(holder := builder bindingAt:#selectionOfChange) isNil ifTrue:[
builder aspectAt:#selectionOfChange put:(holder := ValueHolder new).
].
^ holder
!
valueOfChangeText
|holder|
(holder := builder bindingAt:#valueOfChangeText) isNil ifTrue:[
builder aspectAt:#valueOfChangeText put:(holder := ValueHolder new).
].
^ holder
!
valueOfFilter
|holder|
(holder := builder bindingAt:#valueOfFilter) isNil ifTrue:[
builder aspectAt:#valueOfFilter put:(holder := ValueHolder new).
].
^ holder
!
valueOfHavingChangeSelection
|holder|
(holder := builder bindingAt:#valueOfHavingChangeSelection) isNil ifTrue:[
builder aspectAt:#valueOfHavingChangeSelection put:(holder := false asValue).
].
^ holder
!
valueOfHavingSelection
|holder|
(holder := builder bindingAt:#valueOfHavingSelection) isNil ifTrue:[
builder aspectAt:#valueOfHavingSelection put:(holder := false asValue).
].
^ holder
!
valueOfNotReading
|holder|
(holder := builder bindingAt:#valueOfReading) isNil ifTrue:[
builder aspectAt:#valueOfReading put:(holder := true asValue).
].
^ holder
!
valueOfReadProgress
|holder|
(holder := builder bindingAt:#valueOfReadProgress) isNil ifTrue:[
builder aspectAt:#valueOfReadProgress put:(holder := ValueHolder with: 0).
].
^ holder
! !
!NewChangesBrowser methodsFor:'callbacks'!
changeSelected: lineNr
"show a change in the codeView"
|aStream sawExcla chunk selectedRow changeNr|
lineNr == 0 ifTrue: [^nil].
changeNr := changes indexOf: (self listOfChanges at: lineNr).
aStream := self streamForChange: (changes at: changeNr).
aStream isNil ifTrue:[^ self].
sawExcla := aStream peekFor:(aStream class chunkSeparator).
chunk := aStream nextChunk.
sawExcla ifTrue:[
chunk := aStream nextChunk
].
aStream close.
self valueOfChangeText value:chunk.
(selectedRow := changes at: changeNr ifAbsent: nil) notNil
ifTrue:
[
self valueOfHavingSelection value: true.
self valueOfHavingChangeSelection value:
((selectedRow type = 'method') or: [(selectedRow type = 'class')])
]
! !
!NewChangesBrowser methodsFor:'compiler interface'!
wantChangeLog
"sent by the compiler to ask if a changeLog entry should
be written. Return false here."
^ false
! !
!NewChangesBrowser methodsFor:'help'!
defaultInfoLabel
"get default label for the info bar"
changeFileName asFilename exists ifTrue: [^changeFileName].
^'No change file name defined.'
! !
!NewChangesBrowser methodsFor:'initialization'!
initialize
super initialize.
changes := List new.
changeFileName := (Filename currentDirectory asAbsoluteFilename construct: ObjectMemory nameForChanges) name.
AutoUpdate := AutoUpdate ? false.
compareChanges := true.
ObjectMemory addDependent:self.
! !
!NewChangesBrowser methodsFor:'menu modes'!
autoUpdateMode
"returns whether autoUpdate is on/off"
^AutoUpdate
!
autoUpdateMode: aMode
"sets the autoUpdate to aMode"
AutoUpdate := aMode
! !
!NewChangesBrowser methodsFor:'private'!
applyChange:change
"fileIn a change"
|aStream nm applyAction changeNr|
aStream := self streamForChange:change.
aStream isNil ifTrue:[^ self].
nm := self classNameOfChange:change.
nm notNil ifTrue:[
|cls|
cls := Smalltalk at:(nm asSymbol) ifAbsent:[].
cls notNil ifTrue:[
cls isLoaded ifFalse:[
cls autoload
]
]
].
applyAction := [
|sig|
(skipSignal notNil) ifTrue:[
sig := skipSignal
] ifFalse:[
sig := Object abortSignal
].
sig catch:[
|reader doItChunk methodsForChunk|
"/ a followup methodsFor: chunk ...
change followUp ifTrue:[
methodsForChunk := change chunk.
] ifFalse:[
doItChunk := aStream nextChunk. "/ an empty chunk sometimes ...
doItChunk notEmpty ifTrue:[
Compiler evaluate:doItChunk notifying:self.
] ifFalse:[
methodsForChunk := aStream nextChunk. "/ the real one
]
].
methodsForChunk notNil ifTrue:[
Class methodRedefinitionSignal handle:[:ex |
ex proceedWith:#keep
] do:[
reader := Compiler evaluate:methodsForChunk notifying:self.
reader fileInFrom:aStream notifying:self passChunk:false single:true.
]
]
].
].
"/
"/ if I am showing the changes file, dont update it
"/
changeFileName = ObjectMemory nameForChanges ifTrue:[
Class withoutUpdatingChangesDo:applyAction
] ifFalse:[
applyAction value
].
aStream close
!
autoSelectChange:change
"select a change"
self class autoSelectNext ifTrue:[
((self listOfChanges indexOf: change) <= self listOfChanges size) ifTrue:[
self selectionOfChange value: change.
self changeSelected:(self listOfChanges indexOf: change).
^ self
]
].
self clearCodeView.
self selectionOfChange value:nil
!
autoSelectLast
"select the last change"
self autoSelectChange: self listOfChanges last
!
autoSelectOrEnd:change
"select change or the last"
|last|
last := self listOfChanges size.
change notNil ifTrue:[
self autoSelectChange:change
] ifFalse:[
self selectionOfChange value: (self listOfChanges at: last ifAbsent: nil).
self changeSelected: last
]
!
changeColumn: aColumnLabel add: addOrRemove
addOrRemove
ifTrue:
[
self listOfChangeColumns add:
((self class specOfChangeColumns collect: [:i| i decodeAsLiteralArray]) detect: [:column| column label = aColumnLabel])
]
ifFalse:
[
self listOfChangeColumns remove:
(self listOfChangeColumns detect: [:column| column label = aColumnLabel] ifNone: [^nil])
]
!
checkClassIsLoaded:aClass
|cls|
aClass isMeta ifTrue:[
cls := aClass soleInstance
] ifFalse:[
cls := aClass
].
cls isLoaded ifFalse:[
(self confirm:(cls name , ' is an autoloaded class.\I can only compare the methods texts if its loaded first.\\Load the class first ?') withCRs)
ifTrue:[
cls autoload
]
].
^ cls isLoaded
!
checkIfFileHasChanged
|f info |
Processor removeTimedBlock:autoUpdateBlock.
f := changeFileName asFilename.
(info := f info) isNil ifTrue:[
self newLabel:'unaccessable'
] ifFalse:[
(info modified) > changeFileTimestamp ifTrue:[
self newLabel:'outdated'.
AutoUpdate ifTrue:[
self doReload
]
] ifFalse:[
self newLabel:''
]
].
Processor addTimedBlock:autoUpdateBlock afterSeconds:5.
!
classNameOfChange:changeNr
"return the classname of a change
(for classChanges (i.e. xxx class), the non-metaClassName (i.e. xxx) is returned)"
|name|
name := self fullClassNameOfChange:changeNr.
name isNil ifTrue:[^ nil].
(name endsWith:' class') ifTrue:[
^ name copyWithoutLast:6
].
^ name
!
clearCodeView
self unselectChange.
self valueOfChangeText value: nil
!
compareChange:change
"compare a change with current version"
|aStream chunk sawExcla parseTree thisClass cat oldSource newSource
parser sel oldMethod outcome showDiff d t1 t2 selector isLoaded
method beep|
aStream := self streamForChange:change.
aStream isNil ifTrue:[^ self].
showDiff := false.
change followUp ifFalse:[
sawExcla := aStream peekFor:(aStream class chunkSeparator).
chunk := aStream nextChunk.
] ifTrue:[
chunk := change chunk.
sawExcla := true.
].
beep := false.
sawExcla ifFalse:[
outcome := 'Cannot compare this change\(i.e. this is not a method change)!!'.
parseTree := Parser parseExpression:chunk.
(parseTree notNil and:[parseTree isMessage]) ifTrue:[
((selector := parseTree selector) == #removeSelector:) ifTrue:[
thisClass := (parseTree receiver evaluate).
thisClass isBehavior ifTrue:[
(self checkClassIsLoaded:thisClass) ifTrue:[
selector := (parseTree arg1 evaluate).
(thisClass includesSelector:selector) ifTrue:[
outcome := 'Change removes the #' , selector , ' method from ' , thisClass name.
] ifFalse:[
outcome := 'Change has no effect\(there is no method for #' , selector , ' in ' , thisClass name , ')'
]
] ifFalse:[
beep := true.
outcome := 'Cannot compare this change (compare requires class to be loaded)!!'.
]
]
].
selector == #category: ifTrue:[
parseTree receiver isMessage ifTrue:[
parseTree receiver selector == #compiledMethodAt: ifTrue:[
(method := parseTree receiver evaluate) isMethod ifTrue:[
method category = parseTree arg1 evaluate ifTrue:[
outcome := 'Change has no effect\(same category)'.
] ifFalse:[
outcome := 'Category is different (''' , method category , ''' vs. ''' , parseTree arg1 evaluate , ''')'
]
] ifFalse:[
beep := true.
outcome := 'There is no such method!!'
]
]
]
]
]
] ifTrue:[
parseTree := Parser parseExpression:chunk.
(parseTree notNil
and:[parseTree ~~ #Error
and:[parseTree isMessage]]) ifTrue:[
(parseTree selector == #methodsFor:) ifTrue:[
thisClass := (parseTree receiver evaluate).
thisClass isBehavior ifTrue:[
(isLoaded := self checkClassIsLoaded:thisClass) ifFalse:[
outcome := 'Cannot compare this change\(compare requires class to be loaded)!!'.
].
cat := parseTree arg1 evaluate.
newSource := aStream nextChunk.
parser := Parser parseMethod:newSource in:thisClass.
(parser notNil and:[parser ~~ #Error]) ifTrue:[
sel := parser selector.
oldMethod := thisClass compiledMethodAt:sel.
oldMethod notNil ifTrue:[
(oldMethod category = cat) ifFalse:[
Transcript showCR:'Category changed.'.
].
oldSource := oldMethod source.
(oldSource = newSource) ifTrue:[
outcome := 'Same source.'
] ifFalse:[
oldSource isNil ifTrue:[
beep := true.
outcome := 'No source for compare.'
] ifFalse:[
"/
"/ compare for tabulator <-> space changes
"/ before showing diff ...
"/
t1 := oldSource asCollectionOfLines collect:[:s | s withTabsExpanded].
t2 := newSource asCollectionOfLines collect:[:s | s withTabsExpanded].
t1 = t2 ifTrue:[
outcome := 'Same source.'
] ifFalse:[
outcome := 'Source changed!!'.
showDiff := true.
"/
"/ check if only historyLine diffs
"/
(HistoryManager notNil
and:[HistoryManager isActive]) ifTrue:[
(HistoryManager withoutHistoryLines:newSource)
=
(HistoryManager withoutHistoryLines:oldSource)
ifTrue:[
outcome := 'Same source (history only).'.
showDiff := false.
]
].
]
]
]
] ifFalse:[
isLoaded ifTrue:[
beep := true.
outcome := 'Method does not exist!!'
]
]
] ifFalse:[
outcome := 'Change unparsable!!'
].
(showDiff and:[oldSource notNil and:[newSource notNil]]) ifTrue:[
d := DiffTextView
openOn:oldSource label:'Current version (in image)'
and:newSource label:'Change version'.
d label:'method differences'.
]
] ifFalse:[
beep := true.
outcome := 'Class does not exist!!'
]
] ifFalse:[
beep := true.
outcome := 'Not comparable!!'
]
] ifFalse:[
beep := true.
outcome := 'Not comparable!!'
]
].
aStream close.
showDiff ifFalse:[
beep ifTrue:[
self warn:outcome withCRs.
] ifFalse:[
self information:outcome withCRs.
]
]
!
compressForClass:aClassNameOrNil
"compress the change-set;
this replaces multiple method-changes by the last (i.e. the most recent) change.
If the class argument is nil, compress for all classes.
otherwise, only changes for that class are compressed."
|aStream searchIndex anyMore deleteSet index
str snapshotProto snapshotPrefix snapshotNameIndex fileName|
aStream := FileStream readonlyFileNamed:changeFileName.
aStream isNil ifTrue:[^ self].
aClassNameOrNil isNil ifTrue:[
self newLabel:'compressing...'.
] ifFalse:[
self newLabel:'compressing for ' , aClassNameOrNil.
].
CompressSnapshotInfo == true ifTrue:[
"
get a prototype snapshot record (to be independent of
the actual format ..
"
str := WriteStream on:String new.
Class addChangeRecordForSnapshot:'foo' to:str.
snapshotProto := str contents.
snapshotPrefix := snapshotProto copyTo:10.
snapshotNameIndex := snapshotProto findString:'foo'.
].
self valueOfNotReading value: false.
self valueOfHavingSelection value: false.
self valueOfHavingChangeSelection value: false.
self valueOfReadProgress value: 0.
self readProgressIndicator raise.
self filterLabel label: 'Comp:'; redraw.
self withExecuteCursorDo:[
|numChanges classes selectors types excla sawExcla
changeNr chunk aParseTree parseTreeChunk
thisClass thisSelector codeChunk codeParser
compressThis oldValue|
numChanges := changes size.
classes := Array new:numChanges.
selectors := Array new:numChanges.
types := Array new:numChanges.
"starting at the end, get the change class and change selector;
collect all in classes / selectors"
changeNr := numChanges.
excla := aStream class chunkSeparator.
[changeNr >= 1] whileTrue:[
oldValue := self valueOfReadProgress value.
self valueOfReadProgress value: (100 - ((aStream position/aStream size) * 100) rounded).
oldValue ~~ self valueOfReadProgress value
ifTrue: [self readProgressIndicator redrawEdges;redraw].
aStream position:(changes at: changeNr) position.
sawExcla := aStream peekFor:excla.
chunk := aStream nextChunk.
sawExcla ifTrue:[
"optimize a bit if multiple methods for same category arrive"
(chunk = parseTreeChunk) ifFalse:[
aParseTree := Parser parseExpression:chunk.
parseTreeChunk := chunk
].
(aParseTree notNil
and:[(aParseTree ~~ #Error)
and:[aParseTree isMessage]]) ifTrue:[
(aParseTree selector == #methodsFor:) ifTrue:[
thisClass := (aParseTree receiver evaluate).
codeChunk := aStream nextChunk.
codeParser := Parser
parseMethodSpecification:codeChunk
in:thisClass
ignoreErrors:true
ignoreWarnings:true.
(codeParser notNil and:[codeParser ~~ #Error]) ifTrue:[
selectors at:changeNr put:(codeParser selector).
classes at:changeNr put:thisClass.
types at:changeNr put:#methodsFor
]
]
]
] ifFalse:[
aParseTree := Parser parseExpression:chunk.
parseTreeChunk := chunk.
(aParseTree notNil
and:[(aParseTree ~~ #Error)
and:[aParseTree isMessage]]) ifTrue:[
(aParseTree selector == #removeSelector:) ifTrue:[
selectors at:changeNr put:(aParseTree arg1 value ).
classes at:changeNr put:(aParseTree receiver evaluate).
types at:changeNr put:#removeSelector
]
] ifFalse:[
CompressSnapshotInfo == true ifTrue:[
(chunk startsWith:snapshotPrefix) ifTrue:[
str := chunk readStream position:snapshotNameIndex.
fileName := str upTo:(Character space).
"
kludge to allow use of match-check below
"
selectors at:changeNr put:snapshotPrefix.
classes at:changeNr put:fileName.
]
]
]
].
changeNr := changeNr - 1
].
aStream close.
"for all changes, look for another class/selector occurence later
in the list and, if there is one, add change number to the delete set"
deleteSet := OrderedCollection new.
changeNr := 1.
[changeNr < changes size] whileTrue:[
thisClass := classes at:changeNr.
compressThis := false.
aClassNameOrNil isNil ifTrue:[
compressThis := true
] ifFalse:[
"/ skipping unloaded/unknown classes
thisClass isBehavior ifTrue:[
thisClass isMeta ifTrue:[
compressThis := aClassNameOrNil = thisClass soleInstance name.
] ifFalse:[
compressThis := aClassNameOrNil = thisClass name
]
]
].
compressThis ifTrue:[
thisSelector := selectors at:changeNr.
searchIndex := changeNr.
anyMore := true.
[anyMore] whileTrue:[
searchIndex := classes indexOf:thisClass
startingAt:(searchIndex + 1).
(searchIndex ~~ 0) ifTrue:[
((selectors at:searchIndex) == thisSelector) ifTrue:[
thisClass notNil ifTrue:[
deleteSet add:changeNr.
anyMore := false
]
]
] ifFalse:[
anyMore := false
]
].
].
changeNr := changeNr + 1
].
"finally delete what has been found"
(deleteSet size > 0) ifTrue:[
index := deleteSet size.
[index > 0] whileTrue:[
self silentDeleteChange: (changes at: (deleteSet at:index)).
index := index - 1
].
"self setChangeList"
].
].
self valueOfNotReading value: true.
self filterField raise.
self filterLabel label: 'Filter:'.
self newLabel: ''
!
contractClass:className selector:selector to:maxLen
|s l|
s := className , ' ', selector.
s size > maxLen ifTrue:[
l := maxLen - 1 - selector size max:20.
s := (className contractTo:l) , ' ' , selector.
s size > maxLen ifTrue:[
l := maxLen - 1 - className size max:20.
s := className , ' ', (selector contractTo:l).
s size > maxLen ifTrue:[
s := (className contractTo:(maxLen // 2 - 1)) , ' ' , (selector contractTo:maxLen // 2)
]
]
].
^ s
!
deleteChangesFrom:start to:stop
"delete a range of changes"
self unselectChange.
stop to:start by:-1 do:[:changeNr|
self silentDeleteChange:(self listOfChanges at: changeNr)
]
!
fullClassNameOfChange:change
"return the full classname of a change
(for classChanges (i.e. xxx class), a string ending in ' class' is returned.
- since parsing ascii methods is slow, keep result cached in
changeClassNames for the next query"
|chunk aParseTree recTree sel name arg1Tree isMeta prevMethodDefNr
words changeStream fullParseTree ownerTree ownerName oldDollarSetting|
change isNil ifTrue:[^ nil].
"
first look, if not already known
"
name := change className.
name notNil ifTrue:[^ name].
prevMethodDefNr := changes indexOf: change.
[(changes at:prevMethodDefNr) followUp] whileTrue:[
prevMethodDefNr := prevMethodDefNr - 1.
].
"
get the chunk
"
chunk := (changes at:prevMethodDefNr) chunk.
chunk isNil ifTrue:[^ nil]. "mhmh - empty"
(chunk startsWith:'''---') ifTrue:[
words := chunk asCollectionOfWords.
words size > 2 ifTrue:[
(words at:2) = 'checkin' ifTrue:[
name := words at:3.
change className: name.
^ name
]
].
].
"/ fix it - otherwise, it cannot be parsed
(chunk endsWith:'primitiveDefinitions:') ifTrue:[
chunk := chunk , ''''''
].
(chunk endsWith:'primitiveFunctions:') ifTrue:[
chunk := chunk , ''''''
].
(chunk endsWith:'primitiveVariables:') ifTrue:[
chunk := chunk , ''''''
].
"
use parser to construct a parseTree
"
oldDollarSetting := Parser allowDollarInIdentifier.
[
Parser allowDollarInIdentifier:true.
aParseTree := Parser parseExpression:chunk.
] valueNowOrOnUnwindDo:[
Parser allowDollarInIdentifier:oldDollarSetting
].
(aParseTree isNil or:[aParseTree == #Error]) ifTrue:[
^ nil "seems strange ... (could be a comment)"
].
aParseTree isMessage ifFalse:[
^ nil "very strange ... (whats that ?)"
].
"
ask parser for selector
"
sel := aParseTree selector.
recTree := aParseTree receiver.
"
is it a method-change, methodRemove or comment-change ?
"
(#(#'methodsFor:'
#'privateMethodsFor:'
#'protectedMethodsFor:'
#'publicMethodsFor:'
#'removeSelector:'
#'comment:'
#'primitiveDefinitions:'
#'primitiveFunctions:'
#'primitiveVariables:'
#'renameCategory:to:'
#'instanceVariableNames:'
) includes:sel) ifTrue:[
"
yes, the className is the receiver
"
(recTree notNil and:[recTree ~~ #Error]) ifTrue:[
isMeta := false.
recTree isUnaryMessage ifTrue:[
(recTree selector ~~ #class) ifTrue:[^ nil].
"id class methodsFor:..."
recTree := recTree receiver.
isMeta := true.
].
recTree isPrimary ifTrue:[
name := recTree name.
isMeta ifTrue:[
name := name , ' class'.
].
change className: name.
^ name
]
].
"more strange things"
^ nil
].
"
is it a change in a class-description ?
"
(('subclass:*' match:sel)
or:[('variable*subclass:*' match:sel)]) ifTrue:[
"/ must parse the full changes text, to get
"/ privacy information.
changeStream := self streamForChange:change.
changeStream notNil ifTrue:[
chunk := changeStream nextChunk.
changeStream close.
fullParseTree := Parser parseExpression:chunk.
(fullParseTree isNil or:[fullParseTree == #Error]) ifTrue:[
fullParseTree := nil
].
fullParseTree isMessage ifFalse:[
fullParseTree := nil
].
"/ actually, the nil case cannot happen
fullParseTree notNil ifTrue:[
aParseTree := fullParseTree.
sel := aParseTree selector.
].
].
arg1Tree := aParseTree arg1.
(arg1Tree notNil and:[arg1Tree isConstant]) ifTrue:[
name := arg1Tree value asString.
"/ is it a private-class ?
('*privateIn:' match:sel) ifTrue:[
ownerTree := aParseTree args last.
ownerName := ownerTree name asString.
name := ownerName , '::' , name
].
change className: name.
^ name
].
"very strange"
^ nil
].
"
is it a class remove ?
"
(sel == #removeClass:) ifTrue:[
(recTree notNil
and:[recTree ~~ #Error
and:[recTree isPrimary
and:[recTree name = 'Smalltalk']]]) ifTrue:[
arg1Tree := aParseTree arg1.
(arg1Tree notNil and:[arg1Tree isPrimary]) ifTrue:[
name := arg1Tree name.
change className: name.
^ name
].
]
].
"
is it a method category change ?
"
((sel == #category:)
or:[sel == #privacy:]) ifTrue:[
(recTree notNil
and:[recTree ~~ #Error
and:[recTree isMessage
and:[recTree selector == #compiledMethodAt:]]]) ifTrue:[
isMeta := false.
recTree := recTree receiver.
recTree isUnaryMessage ifTrue:[
(recTree selector ~~ #class) ifTrue:[^ nil].
"id class "
recTree := recTree receiver
].
recTree isPrimary ifTrue:[
isMeta ifTrue:[
name := name , ' class'.
].
name := recTree name.
change className: name.
^ name
]
]
].
^ nil
!
newLabel:how
how size = 0 ifTrue: [^self window label:self class label].
self window label:self class label, '(', how, ')'
!
readChangesFileInBackground:inBackground
"read the changes file, create a list of header-lines (changeChunks)
and a list of chunk-positions (changePositions).
Starting with 2.10.3, the entries are multi-col entries;
the cols are:
1 delta (only if comparing)
'+' -> new method (w.r.t. current state)
'-' -> removed method (w.r.t. current state)
'?' -> class does not exist currently
'=' -> change is same as current methods source
2 class/selector
3 type of change
doit
method
category change
4 timestamp
since comparing slows down startup time, it is now disabled by
default and can be enabled via a toggle."
|aStream maxLen i f|
self valueOfNotReading value: false.
self valueOfHavingSelection value: false.
self valueOfHavingChangeSelection value: false.
editingClassSource := false.
maxLen := 60.
f := changeFileName asFilename.
aStream := f readStream.
aStream isNil ifTrue:[^ nil].
self newLabel:'updating ...'.
i := f info.
changeFileTimestamp := i modified.
self valueOfReadProgress value: 0.
self readProgressIndicator raise.
self filterLabel label: 'Read:'; redraw.
self withReadCursorDo:[
|myProcess myPriority|
"
this is a time consuming operation (especially, if reading an
NFS-mounted directory; therefore lower my priority ...
"
inBackground ifTrue:[
myProcess := Processor activeProcess.
myPriority := myProcess priority.
myProcess priority:(Processor userBackgroundPriority).
].
[
|excla timeStampInfo|
excla := aStream class chunkSeparator.
[aStream atEnd] whileFalse:[
|change changeDelta changeString changeType changeCategory
line s l changeClass sawExcla category
chunkText chunkPos sel oldValue|
change := Change new.
"
get a chunk (separated by excla)
"
oldValue := self valueOfReadProgress value.
self valueOfReadProgress value: (((aStream position/aStream size) * 100) rounded).
oldValue ~~ self valueOfReadProgress value
ifTrue: [self readProgressIndicator redrawEdges;redraw].
aStream skipSeparators.
chunkPos := aStream position.
sawExcla := aStream peekFor:excla.
chunkText := aStream nextChunk.
chunkText notNil ifTrue:[
|index headerLine cls|
(chunkText startsWith:'''---- timestamp ') ifTrue:[
timeStampInfo := (chunkText copyFrom:16 to:(chunkText size - 6)) withoutSpaces.
] ifFalse:[
"
only first line is saved in changeChunks ...
"
index := chunkText indexOf:(Character cr).
(index ~~ 0) ifTrue:[
chunkText := chunkText copyTo:(index - 1).
"take care for comment changes - must still be a
valid expression for classNameOfChange: to work"
(chunkText endsWith:'comment:''') ifTrue:[
chunkText := chunkText , '...'''
].
(chunkText endsWith:'primitiveDefinitions:''') ifTrue:[
sel := 'primitiveDefinitions:'.
chunkText := chunkText copyWithoutLast:1
].
(chunkText endsWith:'primitiveVariables:''') ifTrue:[
sel := 'primitiveVariables:'.
chunkText := chunkText copyWithoutLast:1
].
(chunkText endsWith:'primitiveFunctions:''') ifTrue:[
sel := 'primitiveFunctions:'.
chunkText := chunkText copyWithoutLast:1
].
].
change chunk: chunkText.
change position: chunkPos.
change timeStamp: timeStampInfo.
change followUp: false.
headerLine := nil.
changeDelta := ' '.
sawExcla ifFalse:[
(chunkText startsWith:'''---- snap') ifTrue:[
changeType := ''.
headerLine := chunkText.
changeString := (chunkText contractTo:maxLen).
timeStampInfo := nil.
] ifFalse:[
|p cls|
headerLine := chunkText , ' (doIt)'.
"
first, assume doIt - then lets have a more detailed look ...
"
((chunkText startsWith:'''---- file')
or:[(chunkText startsWith:'''---- check')]) ifTrue:[
changeType := ''.
timeStampInfo := nil.
] ifFalse:[
changeType := 'doIt'.
].
changeString := (chunkText contractTo:maxLen).
p := Parser parseExpression:chunkText inNameSpace:Smalltalk.
(p notNil
and:[p ~~ #Error
and:[p isMessage]]) ifTrue:[
sel := p selector.
].
(sel == #removeSelector:) ifTrue:[
p receiver isUnaryMessage ifTrue:[
cls := p receiver receiver name.
changeClass := (Smalltalk classNamed:cls) class.
cls := cls , ' class'.
] ifFalse:[
cls := p receiver name.
changeClass := (Smalltalk classNamed:cls)
].
sel := (p args at:1) evaluate.
compareChanges ifTrue:[
(changeClass isNil or:[changeClass isLoaded not]) ifTrue:[
changeDelta := '?'
] ifFalse:[
(changeClass implements:sel asSymbol) ifTrue:[
changeDelta := '-'.
]
]
].
changeType := 'remove'.
changeString := self contractClass:cls selector:sel to:maxLen.
].
(p ~~ #Error
and:[p isMessage
and:[p receiver isMessage
and:[p receiver selector == #compiledMethodAt:]]]) ifTrue:[
p receiver receiver isUnaryMessage ifTrue:[
cls := p receiver receiver receiver name.
changeClass := (Smalltalk classNamed:cls) class.
cls := cls , ' class'.
] ifFalse:[
cls := p receiver receiver name.
changeClass := (Smalltalk classNamed:cls)
].
(sel == #category:) ifTrue:[
sel := (p receiver args at:1) evaluate.
changeType := '(category change)'.
changeString := self contractClass:cls selector:sel to:maxLen.
].
(sel == #privacy:) ifTrue:[
sel := (p receiver args at:1) evaluate.
changeType := 'privacy change'.
changeString := self contractClass:cls selector:sel to:maxLen.
].
].
(#(#'subclass:'
#'variableSubclass:'
#'variableByteSubclass:'
#'variableWordSubclass:'
#'variableLongSubclass:'
#'variableFloatSubclass:'
#'variableDoubleSubclass:'
#'primitiveDefinitions:'
#'primitiveFunctions:'
#'primitiveVariables:'
) includes:sel) ifTrue:[
changeType := 'class definition'.
].
]
] ifTrue:[
|done first p className cls text methodPos|
"
method definitions actually consist of
two (or more) chunks; skip next chunk(s)
up to an empty one.
The system only writes one chunk,
and we cannot handle more in this ChangesBrowser ....
"
className := nil.
p := Parser parseExpression:chunkText inNameSpace:Smalltalk.
(p notNil and:[p ~~ #Error]) ifTrue:[
sel := p selector.
(sel == #methodsFor:) ifTrue:[
p receiver isUnaryMessage ifTrue:[
className := p receiver receiver name.
changeClass := (Smalltalk classNamed:className) class.
className := className , ' class'.
] ifFalse:[
className := p receiver name.
changeClass := Smalltalk classNamed:className
].
category := (p args at:1) evaluate.
].
].
done := false.
first := true.
[done] whileFalse:[
changeDelta := ' '.
methodPos := aStream position.
text := aStream nextChunk.
text isNil ifTrue:[
done := true
] ifFalse:[
done := text isEmpty
].
done ifFalse:[
first ifFalse:[
change := Change new.
change chunk: chunkText.
change string:changeString.
change position: methodPos.
change timeStamp: timeStampInfo.
change followUp: true.
editingClassSource := true.
].
first := false.
"
try to find the selector
"
sel := nil.
className notNil ifTrue:[
p := Parser
parseMethodSpecification:text
in:nil
ignoreErrors:true
ignoreWarnings:true.
(p notNil and:[p ~~ #Error]) ifTrue:[
sel := p selector.
]
].
sel isNil ifTrue:[
changeString := (chunkText contractTo:maxLen).
changeType := 'change'.
headerLine := chunkText , ' (change)'.
] ifFalse:[
changeString := self contractClass:className selector:sel to:maxLen.
changeType := 'method definition'.
changeCategory := category.
headerLine := className , ' ' , sel , ' ' , '(change category: ''' , category , ''')'.
].
compareChanges ifTrue:[
changeClass isNil ifFalse:[
changeClass isMeta ifTrue:[
cls := changeClass soleInstance
] ifFalse:[
cls := changeClass
].
].
(changeClass isNil or:[cls isLoaded not]) ifTrue:[
changeDelta := '?'
] ifFalse:[
(changeClass implements:sel asSymbol) ifFalse:[
changeDelta := '+'.
] ifTrue:[
|m currentText t1 t2|
m := changeClass compiledMethodAt:sel asSymbol.
currentText := m source.
currentText notNil ifTrue:[
text asString = currentText asString ifTrue:[
changeDelta := '='
] ifFalse:[
t1 := currentText asCollectionOfLines collect:[:s | s withTabsExpanded].
t2 := text asCollectionOfLines collect:[:s | s withTabsExpanded].
t1 = t2 ifTrue:[
changeDelta := '='
]
]
]
]
]
].
change delta:changeDelta.
change string:changeString.
change type:changeType.
change category: changeCategory.
change timeStamp:timeStampInfo.
changes add:change.
].
changeString := nil.
headerLine := nil.
]
].
changeString notNil ifTrue:[
change delta:changeDelta.
change string:changeString.
change type:changeType.
change timeStamp:timeStampInfo.
changes add:change.
] ifFalse:[
headerLine notNil ifTrue:[
changes add: change.
]
]
]
]
].
modified := false.
] valueNowOrOnUnwindDo:[
aStream close.
inBackground ifTrue:[myProcess priority:myPriority].
].
].
self setChangeList.
self setChangeSelection.
self valueOfNotReading value: true.
self filterField raise.
self filterLabel label: 'Filter:'.
self checkIfFileHasChanged.
!
selectorOfMethodChange:change
"return a method-changes selector, or nil if its not a methodChange"
|source parser sel|
source := self sourceOfMethodChange:change.
source isNil ifTrue:[^ nil].
parser := Parser
parseMethod:source
in:nil
ignoreErrors:true
ignoreWarnings:true.
(parser notNil and:[parser ~~ #Error]) ifTrue:[
sel := parser selector.
].
^ sel
!
setChangeList
"extract type-information from changes and stuff into top selection
view"
self valueOfFilter value: nil.
self listOfChanges contents: changes.
changes size == 0
ifTrue:
[
self unselectChange.
self valueOfHavingSelection value: false.
self valueOfHavingChangeSelection value: false.
]
ifFalse:
[
self selectionOfChange value: (self listOfChanges size > 0 ifTrue: [self listOfChanges last] ifFalse: [nil])
]
!
setChangeSelection
"extract type-information from changes and stuff into top selection
view"
changes size == 0
ifTrue:
[
self unselectChange.
self valueOfHavingSelection value: false.
self valueOfHavingChangeSelection value: false.
]
ifFalse:
[
self selectionOfChange value: self listOfChanges last
]
!
silentDeleteChange:change
"delete a change do not update changeListView"
modified := true.
changes remove:change.
self listOfChanges remove:change
!
silentDeleteChangesFor:aClassName from:start to:stop
"delete changes for a given class in a range.
Return the number of deleted changes."
|thisClassName index numDeleted|
numDeleted := 0.
index := stop.
[index >= start] whileTrue:[
thisClassName := self classNameOfChange:(self listOfChanges at: index).
thisClassName = aClassName ifTrue:[
self silentDeleteChange:(self listOfChanges at: index).
numDeleted := numDeleted + 1.
].
index := index - 1
].
^ numDeleted
!
sourceOfMethodChange:change
"return a method-changes source code, or nil if its not a methodChange."
|aStream chunk sawExcla parseTree sourceChunk|
aStream := self streamForChange:change.
aStream isNil ifTrue:[^ nil].
change followUp ifFalse:[
sawExcla := aStream peekFor:(aStream class chunkSeparator).
chunk := aStream nextChunk.
] ifTrue:[
chunk := change chunk.
sawExcla := true.
].
sawExcla ifTrue:[
parseTree := Parser parseExpression:chunk.
(parseTree notNil and:[parseTree isMessage]) ifTrue:[
(parseTree selector == #methodsFor:) ifTrue:[
sourceChunk := aStream nextChunk.
]
].
].
aStream close.
^ sourceChunk
!
streamForChange:change
"answer a stream for change"
|aStream|
aStream := FileStream readonlyFileNamed:changeFileName.
aStream isNil ifTrue:[^ nil].
aStream position:change position.
^ aStream
!
unselectChange
"common unselect"
self selectionOfChange value: nil
!
withSelectedChangeDo:aBlock
"just a helper, check for a selected change and evaluate aBlock
with busy cursor"
|change|
(change := self selectionOfChange value) notNil
ifTrue:[
self withExecuteCursorDo:[aBlock value:change]
]
! !
!NewChangesBrowser methodsFor:'startup / release'!
closeRequest
self valueOfNotReading value ifFalse: [^nil].
modified ifTrue:[
(OptionBox
request:(resources at:'Changes file was modified!!') withCRs
label:'Changes Browser'
form:(WarningBox iconBitmap)
buttonLabels:#('Cancel' ' Waste it and proceed ')
values:#(#abort #ignore)
default:#save
) == #abort ifTrue:[^self].
].
super closeRequest
!
postOpenWith:aBuilder
super postOpenWith:aBuilder.
builder namedComponents do:
[:aView|
aView allSubViewsDo:
[:v|
v redraw
]
].
self readChangesFileInBackground:true.
autoUpdateBlock := [self checkIfFileHasChanged].
Processor addTimedBlock:autoUpdateBlock afterSeconds:5.
self updateInfoLabel.
self filterField entryCompletionBlock:
(filterCompletionBlock := [:value|
|filter filters i changesCopy|
self clearCodeView.
((filter := self filterField contents) notNil and:
[(filters := filter asArrayOfSubstrings) size > 0]) ifTrue:
[
i := 0.
changesCopy := changes copy.
filters do:
[:filter|
i := i + 1.
changesCopy contents:
(changesCopy select: [:row|
filter match: (row string asArrayOfSubstrings at: i ifAbsent: [''''])])
].
self listOfChanges contents: changesCopy
]
ifFalse:
[
self setChangeList
].
self autoSelectLast.
])
!
uninitialize
Processor removeTimedBlock:autoUpdateBlock.
ObjectMemory removeDependent:self
! !
!NewChangesBrowser methodsFor:'user actions'!
doApply
"user wants a change to be applied"
self withSelectedChangeDo:[:change|
skipSignal := nil.
self applyChange:change.
self autoSelectChange: (self listOfChanges at: (self listOfChanges indexOf: change) ifAbsent: nil)
]
!
doApplyAll
"user wants all changes to be applied"
self withExecuteCursorDo:[
|change|
self clearCodeView.
skipSignal isNil ifTrue:[skipSignal := Signal new].
1 to:self listOfChanges size do:[:changeNr |
self selectionOfChange value:(change := self listOfChanges at: changeNr).
self applyChange:change
].
self autoSelectLast
]
!
doApplyForClassToEnd
"user wants all changes for this class from changeNr to be applied"
self withSelectedChangeDo:[:change|
|thisClassName classNameToApply lastChange change2|
(classNameToApply := self classNameOfChange:change) notNil
ifTrue:
[
self clearCodeView.
skipSignal isNil ifTrue:[skipSignal := Signal new].
(self listOfChanges indexOf: change) to:self listOfChanges size do:
[:changeNr|
change2 := self listOfChanges indexOf: changeNr.
(thisClassName := self classNameOfChange:change2) = classNameToApply
ifTrue:
[
self selectionOfChange value: change2.
self applyChange:change2.
lastChange := change2
]
].
self autoSelectChange:lastChange.
]
]
!
doApplyFromLastSnapshot
self autoSelectLast.
(self doFindSnapshot: 'last') ifTrue: [self doApplyToEnd]
!
doApplyToEnd
"user wants all changes from changeNr to be applied"
self withSelectedChangeDo:[:change|
self clearCodeView.
skipSignal isNil ifTrue:[skipSignal := Signal new].
(self listOfChanges indexOf: change) to: self listOfChanges size do:[:changeNr|
self selectionOfChange value:(self listOfChanges at: changeNr).
self applyChange:(self listOfChanges at: changeNr)
].
self autoSelectChange:self listOfChanges last
]
!
doBrowseClass
"user wants a browser on the class of a change"
|className cls isMeta|
className := self fullClassNameOfChange:self selectionOfChange value.
className notNil ifTrue:[
isMeta := false.
(className endsWith:' class') ifTrue:[
className := className copyWithoutLast:6.
isMeta := true.
].
(cls := Smalltalk classNamed:className) notNil ifTrue:[
isMeta ifTrue:[cls := cls class].
SystemBrowser
openInClass:cls
selector:(self selectorOfMethodChange:self selectionOfChange value)
]
]
!
doCompare
"compare change with current system version
- give a note in transcript"
|change|
(change := self selectionOfChange value) notNil ifTrue:[
self withExecuteCursorDo:[self compareChange:change]
].
self newLabel:''
!
doCompress
"compress the change-set; this replaces multiple method-changes by the last
(i.e. the most recent) change"
|changesSizeBefore|
changesSizeBefore := changes size.
self setChangeList.
self unselectChange.
self compressForClass:nil.
filterCompletionBlock value: self valueOfFilter value.
self updateInfoLabel.
self information:
'Compression Rate: ', (((changesSizeBefore - changes size)/changesSizeBefore) * 100) rounded printString, '%\' withCRs,
'Compressed Changes: ', (changesSizeBefore - changes size) printString, ' from ', changesSizeBefore printString
!
doCompressForClass
"compress changes for the selected class.
this replaces multiple method-changes by the last (i.e. the most recent) change."
self withSelectedChangeDo:[:change|
| classNameToCompress |
(classNameToCompress := self classNameOfChange:change) notNil ifTrue:[
self compressForClass:classNameToCompress.
filterCompletionBlock value: self valueOfFilter value.
self autoSelectLast
]
]
!
doDelete
"delete currently selected change"
|change selectionIndex|
(change := self selectionOfChange value) notNil ifTrue:[
selectionIndex := self listOfChanges indexOf: change.
self unselectChange.
self silentDeleteChange:change.
self autoSelectOrEnd: (self listOfChanges at: selectionIndex ifAbsent: [nil]).
]
!
doDeleteForClassToEnd
"delete rest of changes with same class as currently selected change"
self withSelectedChangeDo:[:change|
|classNameToDelete|
(classNameToDelete := self classNameOfChange:change) notNil ifTrue:[
self unselectChange.
self silentDeleteChangesFor:classNameToDelete
from:(self listOfChanges indexOf: change)
to:self listOfChanges size.
self autoSelectOrEnd: nil
]
]
!
doDeleteToEnd
"delete all changes from current to the end"
|changeNr|
changeNr := (self listOfChanges indexOf: self selectionOfChange value).
changeNr ~~ 0 ifTrue:[
self deleteChangesFrom:changeNr to: self listOfChanges size.
self clearCodeView.
self autoSelectOrEnd: nil
]
!
doFindSnapshot: what
self listOfChanges detect: [:change| change type = 'image'] ifNone: [^self warn: 'No snapshot found!!'].
self withSelectedChangeDo:[:change|
|snapshotNr snapshotFound|
snapshotNr := self listOfChanges indexOf: change.
snapshotFound := false.
[snapshotNr > 0 and: [snapshotFound not]]
whileTrue:
[
what = 'last'
ifTrue:
[
snapshotNr := snapshotNr - 1.
snapshotNr == 0 ifTrue: [snapshotNr := self listOfChanges size].
]
ifFalse:
[
snapshotNr := snapshotNr + 1.
snapshotNr == self listOfChanges size ifTrue: [snapshotNr := 0].
].
(self listOfChanges at: snapshotNr ifAbsent: [^self autoSelectChange: (what = 'last' ifTrue: [self listOfChanges last] ifFalse: [self listOfChanges first])]) type = 'image'
ifTrue:
[
snapshotFound := true.
self autoSelectChange:(self listOfChanges at: snapshotNr)
]
]
]
!
doLoadFrom
|fileName|
(fileName :=
(FileSelectionBrowser
request: 'Load Changes List From'
fileName: changeFileName
withFileFilters: #('c*'))) notNil
ifTrue:
[
changeFileName := fileName.
changes removeAll.
self readChangesFileInBackground:true.
self unselectChange.
Processor addTimedBlock:autoUpdateBlock afterSeconds:5.
self updateInfoLabel.
self autoSelectLast
]
!
doReload
"reloads the changes-file"
changes removeAll.
self unselectChange.
self readChangesFileInBackground:true.
filterCompletionBlock value: self valueOfFilter value.
self autoSelectLast
!
doSave
"write back the changes file. To avoid problems when the disk is full
or a crash occurs while writing (well, or someone kills us),
first write the stuff to a new temporary file. If this works ok,
rename the old change-file to a .bak file and finally rename the
tempfile back to the change-file.
That way, if anything happens, either the original file is left unchanged,
or we have at least a backup of the previous change file."
|inStream outStream tempfile stamp f|
self valueOfNotReading value ifFalse: [^nil].
editingClassSource ifTrue:[
(self confirm:'You are editing a classes sourceFile (not a changeFile) !!\Are you certain, you want to overwrite it ?' withCRs)
ifFalse:[
^ false
]
].
tempfile := Filename newTemporaryIn:nil.
tempfile exists ifTrue:[tempfile remove].
outStream := tempfile writeStream.
outStream isNil ifTrue:[
self warn:'Cannot create temporary file in current directory.'.
^ false
].
changeFileName asFilename copyTo: changeFileName,'S'.
inStream := FileStream readonlyFileNamed:changeFileName.
inStream isNil ifTrue:[^ false].
self withCursor:(Cursor write) do:[
|excla sawExcla done chunk
nChanges "{Class:SmallInteger}" |
Stream writeErrorSignal handle:[:ex |
self warn:('Could not update the changes file.\\' , ex errorString) withCRs.
^ false
] do:[
excla := inStream class chunkSeparator.
nChanges := changes size.
1 to:nChanges do:[:index |
inStream position: (changes at: index) position.
sawExcla := inStream peekFor:excla.
chunk := inStream nextChunk.
(stamp := (changes at:index) timeStamp) notNil ifTrue:[
outStream nextPutAll:'''---- timestamp ' , stamp , ' ----'''.
outStream nextPut:excla; cr.
].
sawExcla ifTrue:[
outStream nextPut:excla.
outStream nextChunkPut:chunk.
outStream cr.
"
a method-definition chunk - skip followups
"
done := false.
[done] whileFalse:[
chunk := inStream nextChunk.
chunk isNil ifTrue:[
done := true
] ifFalse:[
outStream nextChunkPut:chunk.
outStream cr.
done := chunk isEmpty
]
].
] ifFalse:[
outStream nextChunkPut:chunk.
outStream cr
]
].
outStream close.
inStream close.
].
f := changeFileName asFilename.
f renameTo:(f withSuffix:'bak').
tempfile renameTo:changeFileName.
modified := false
].
^ true
!
doSaveAs
|fileName|
(fileName :=
(FileSelectionBrowser
request: 'Load Changes From'
fileName: changeFileName
withFileFilters: #('changes*'))) notNil
ifTrue:
[
(fileName := (Filename named:fileName) writeStream) notNil
ifTrue:
[
fileName close.
changeFileName := fileName pathName.
self doSave.
self updateInfoLabel
]
]
! !
!NewChangesBrowser::Change methodsFor:'accessing'!
category
^category
!
category: aValue
category := aValue
!
chunk
^chunk
!
chunk: aValue
chunk := aValue
!
className
^className
!
className: aValue
className := aValue
!
delta
delta size = 0 ifTrue: [^''].
delta = '=' ifTrue: [^'Current'].
delta = '?' ifTrue: [^'No class'].
delta = '-' ifTrue: [^'Removed'].
delta = '+' ifTrue: [^'New'].
^delta
!
delta: aValue
delta := aValue
!
followUp
^followUp
!
followUp: aValue
followUp := aValue
!
listColor
(string at: 3) ~~ $- ifTrue:
[
(self type = 'class') ifTrue: [^Color gray].
^Color white
].
(string includesMatchString: '---- s') ifTrue: [^Color red].
(string includesMatchString: '---- f') ifTrue: [^Color cyan: 100 magenta: 20 yellow: 20].
(string includesMatchString: '---- c') ifTrue: [^Color blue].
^Color white
!
position
^position
!
position: aValue
position := aValue
!
string
^string
!
string: aValue
string := aValue
!
timeStamp
^timeStamp
!
timeStamp: aValue
timeStamp := aValue
!
type
(type = 'doIt' or: [type = 'remove']) ifTrue: [^'class'].
(type size = 0) ifTrue: [(string includesMatchString: '---- s') ifTrue: [^'image'] ifFalse: [^'source']].
(self category = nil) ifTrue: [^'class'].
^'method'
!
type: aValue
type := aValue
! !
!NewChangesBrowser class methodsFor:'documentation'!
version
^ '$Header: /cvs/stx/stx/libtool/NewChangesBrowser.st,v 1.1 1998-04-14 13:08:18 tz Exp $'
! !