VersionDiffBrowser.st
author Claus Gittinger <cg@exept.de>
Tue, 22 Aug 2000 12:55:30 +0200
changeset 2726 ecf8db9dbbb1
parent 2725 e2dc264e8c9c
child 2730 0bf87aa1ac49
permissions -rw-r--r--
*** empty log message ***

"
 COPYRIGHT (c) 2000 by eXept Software AG
              All Rights Reserved

 This software is furnished under a license and may be used
 only in accordance with the terms of that license and with the
 inclusion of the above copyright notice.   This software may not
 be provided or otherwise made available to, or used by, any
 other person.  No title to or ownership of the software is
 hereby transferred.
"


"{ Package: 'stx:libtool' }"

ApplicationModel subclass:#VersionDiffBrowser
	instanceVariableNames:'classChangeSet diffTextView'
	classVariableNames:''
	poolDictionaries:''
	category:'Interface-Browsers'
!

HierarchicalItem subclass:#ClassChangeSet
	instanceVariableNames:'classBeingCompared labelA labelB diffSet onlyInA onlyInB
		changedMethods'
	classVariableNames:''
	poolDictionaries:''
	privateIn:VersionDiffBrowser
!

VersionDiffBrowser comment:'some comment
'!

!VersionDiffBrowser class methodsFor:'documentation'!

copyright
"
 COPYRIGHT (c) 2000 by eXept Software AG
              All Rights Reserved

 This software is furnished under a license and may be used
 only in accordance with the terms of that license and with the
 inclusion of the above copyright notice.   This software may not
 be provided or otherwise made available to, or used by, any
 other person.  No title to or ownership of the software is
 hereby transferred.
"

!

documentation
"
    This is not yet finished (work in progress).

    A browser to show differences between class versions,
    allowing easy comparison; will also eventually add capabilities
    to checkIn / load classes into / from the repository.

    [see also:]

    [instance variables:]

    [class variables:]
"


! !

!VersionDiffBrowser 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:VersionDiffBrowser andSelector:#windowSpec
     VersionDiffBrowser new openInterface:#windowSpec
     VersionDiffBrowser open
    "

    <resource: #canvas>

    ^ 
     #(#FullSpec
        #name: #windowSpec
        #window: 
       #(#WindowSpec
          #label: 'Version DiffBrowser'
          #name: 'Version DiffBrowser'
          #min: #(#Point 10 10)
          #max: #(#Point nil nil)
          #bounds: #(#Rectangle 12 22 877 526)
          #menu: #mainMenu
        )
        #component: 
       #(#SpecCollection
          #collection: #(
           #(#VariableVerticalPanelSpec
              #name: 'VariableVerticalPanel1'
              #layout: #(#LayoutFrame 0 0.0 0 0.0 0 1.0 0 1.0)
              #component: 
             #(#SpecCollection
                #collection: #(
                 #(#HorizontalPanelViewSpec
                    #name: 'TopHorizontalPanel'
                    #horizontalLayout: #fit
                    #verticalLayout: #fit
                    #horizontalSpace: 3
                    #verticalSpace: 3
                    #component: 
                   #(#SpecCollection
                      #collection: #(
                       #(#ViewSpec
                          #name: 'BoxA'
                          #level: 0
                          #visibilityChannel: #boxAVisible
                          #component: 
                         #(#SpecCollection
                            #collection: #(
                             #(#LabelSpec
                                #label: 'Only in A'
                                #name: 'OnlyInALabel'
                                #layout: #(#LayoutFrame 0 0.0 0 0.0 0 1.0 20 0)
                                #translateLabel: true
                                #labelChannel: #onlyInALabelHolder
                              )
                             #(#SequenceViewSpec
                                #name: 'ListA'
                                #layout: #(#LayoutFrame 0 0.0 20 0.0 0 1.0 -3 1.0)
                                #model: #methodsOnlyInASelection
                                #menu: #menuA
                                #hasHorizontalScrollBar: true
                                #hasVerticalScrollBar: true
                                #miniScrollerHorizontal: true
                                #valueChangeSelector: #methodsOnlyInASelectionChanged
                                #useIndex: true
                                #sequenceList: #methodsOnlyInA
                              )
                             )
                           
                          )
                          #extent: #(#Point 286 252)
                        )
                       #(#ViewSpec
                          #name: 'BoxM'
                          #visibilityChannel: #boxMVisible
                          #component: 
                         #(#SpecCollection
                            #collection: #(
                             #(#LabelSpec
                                #label: 'Changed'
                                #name: 'Label2'
                                #layout: #(#LayoutFrame 0 0.0 0 0.0 0 1.0 20 0)
                                #translateLabel: true
                                #labelChannel: #changedLabelHolder
                              )
                             #(#SequenceViewSpec
                                #name: 'ListM'
                                #layout: #(#LayoutFrame 0 0.0 20 0.0 0 1.0 -3 1.0)
                                #model: #methodsChangedSelection
                                #menu: #menuM
                                #hasHorizontalScrollBar: true
                                #hasVerticalScrollBar: true
                                #miniScrollerHorizontal: true
                                #valueChangeSelector: #methodsChangedSelectionChanged
                                #useIndex: true
                                #sequenceList: #methodsChanged
                              )
                             )
                           
                          )
                          #extent: #(#Point 286 252)
                        )
                       #(#ViewSpec
                          #name: 'BoxB'
                          #visibilityChannel: #boxBVisible
                          #component: 
                         #(#SpecCollection
                            #collection: #(
                             #(#LabelSpec
                                #label: 'Only in B'
                                #name: 'OnlyInBLabel'
                                #layout: #(#LayoutFrame 0 0.0 0 0.0 0 1.0 20 0)
                                #translateLabel: true
                                #labelChannel: #onlyInBLabelHolder
                              )
                             #(#SequenceViewSpec
                                #name: 'ListB'
                                #layout: #(#LayoutFrame 0 0.0 20 0.0 0 1.0 -3 1.0)
                                #model: #methodsOnlyInBSelection
                                #menu: #menuB
                                #hasHorizontalScrollBar: true
                                #hasVerticalScrollBar: true
                                #miniScrollerHorizontal: true
                                #valueChangeSelector: #methodsOnlyInBSelectionChanged
                                #useIndex: true
                                #sequenceList: #methodsOnlyInB
                              )
                             )
                           
                          )
                          #extent: #(#Point 287 252)
                        )
                       )
                     
                    )
                  )
                 #(#ViewSpec
                    #name: 'Box4'
                    #component: 
                   #(#SpecCollection
                      #collection: #(
                       #(#ViewSpec
                          #name: 'diffTextViewBox'
                          #layout: #(#LayoutFrame 0 0.0 0 0.0 0 1.0 0 1.0)
                          #initiallyInvisible: true
                          #component: 
                         #(#SpecCollection
                            #collection: #(
                             #(#LabelSpec
                                #label: 'A'
                                #name: 'DiffTextLabelA'
                                #layout: #(#LayoutFrame 0 0 0 0 0 0.5 20 0)
                                #translateLabel: true
                                #labelChannel: #diffTextLabelA
                              )
                             #(#LabelSpec
                                #label: 'B'
                                #name: 'DiffTextLabelB'
                                #layout: #(#LayoutFrame 0 0.5 0 0 0 1 20 0)
                                #translateLabel: true
                                #labelChannel: #diffTextLabelB
                              )
                             #(#ArbitraryComponentSpec
                                #name: 'diffTextView'
                                #layout: #(#LayoutFrame 0 0.0 20 0.0 0 1.0 0 1.0)
                                #hasBorder: false
                                #component: #diffTextView
                              )
                             )
                           
                          )
                        )
                       #(#TextEditorSpec
                          #name: 'singleTextView'
                          #layout: #(#LayoutFrame 0 0.0 0 0.0 0 1.0 0 1.0)
                          #model: #methodText
                          #hasHorizontalScrollBar: true
                          #hasVerticalScrollBar: true
                          #miniScrollerHorizontal: true
                        )
                       )
                     
                    )
                  )
                 )
               
              )
              #handles: #(#Any 0.5 1.0)
            )
           )
         
        )
      )
! !

!VersionDiffBrowser class methodsFor:'menu specs'!

mainMenu
    "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:VersionDiffBrowser andSelector:#mainMenu
     (Menu new fromLiteralArrayEncoding:(VersionDiffBrowser mainMenu)) startUp
    "

    <resource: #menu>

    ^ 
     #(#Menu
        #(
         #(#MenuItem
            #label: 'File'
            #translateLabel: true
            #submenu: 
           #(#Menu
              #(
               #(#MenuItem
                  #label: 'Create PatchFile'
                  #translateLabel: true
                  #value: #createPatchFile
                )
               #(#MenuItem
                  #label: '-'
                )
               #(#MenuItem
                  #label: 'Exit'
                  #translateLabel: true
                  #value: #closeRequest
                )
               )
              nil
              nil
            )
          )
         #(#MenuItem
            #label: 'Help'
            #translateLabel: true
            #startGroup: #right
            #submenu: 
           #(#Menu
              #(
               #(#MenuItem
                  #label: 'Documentation'
                  #translateLabel: true
                  #value: #openDocumentation
                )
               #(#MenuItem
                  #label: '-'
                )
               #(#MenuItem
                  #label: 'About this Application'
                  #translateLabel: true
                  #value: #openAboutThisApplication
                )
               )
              nil
              nil
            )
          )
         )
        nil
        nil
      )
!

menuA
    "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:VersionDiffBrowser andSelector:#menu1
     (Menu new fromLiteralArrayEncoding:(VersionDiffBrowser menu1)) startUp
    "

    <resource: #menu>

    ^ 
     #(#Menu
        #(
         #(#MenuItem
            #label: 'Inspect'
            #translateLabel: true
            #value: #inspectSelectedChangeInA
            #choiceValue: 'nil "UndefinedObject" '
          )
         #(#MenuItem
            #label: '-'
          )
         #(#MenuItem
            #label: 'Browse'
            #translateLabel: true
            #value: #browseClassInA
          )
         )
        nil
        nil
      )
!

menuB
    "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:VersionDiffBrowser andSelector:#menuB
     (Menu new fromLiteralArrayEncoding:(VersionDiffBrowser menuB)) startUp
    "

    <resource: #menu>

    ^ 
     #(#Menu
        #(
         #(#MenuItem
            #label: 'Inspect'
            #translateLabel: true
            #value: #inspectSelectedChangeInB
            #choiceValue: 'nil "UndefinedObject" '
          )
         #(#MenuItem
            #label: '-'
          )
         #(#MenuItem
            #label: 'Browse'
            #translateLabel: true
            #value: #browseClassInB
          )
         )
        nil
        nil
      )
!

menuM
    "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:VersionDiffBrowser andSelector:#menu1
     (Menu new fromLiteralArrayEncoding:(VersionDiffBrowser menu1)) startUp
    "

    <resource: #menu>

    ^ 
     #(#Menu
        #(
         #(#MenuItem
            #label: 'Inspect both'
            #translateLabel: true
            #value: #inspectSelectedChangeInM
            #choiceValue: 'nil "UndefinedObject" '
          )
         #(#MenuItem
            #label: '-'
          )
         #(#MenuItem
            #label: 'Browse'
            #translateLabel: true
            #value: #browseClassInM
          )
         )
        nil
        nil
      )
! !

!VersionDiffBrowser class methodsFor:'plugIn spec'!

aspectSelectors
    "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."

    "Return a description of exported aspects;
     these can be connected to aspects of an embedding application
     (if this app is embedded in a subCanvas)."

    ^ #(
        #changedLabelHolder
        #onlyInALabelHolder
        #onlyInBLabelHolder
      ).

! !

!VersionDiffBrowser class methodsFor:'startup'!

openOnClass:aClass againstVersion:aVersionA 
"
create an VersionDiffBrowser instance and set the class change set of the
browser. The class diff set is generated from classes current against some version
via the source code manager .

<return: VersionDiffBrowser>
"
    |theBrowser|

    theBrowser := self new.
    theBrowser allButOpen.
    theBrowser setupForClass:aClass againstVersion:aVersionA.
    theBrowser openWindow.
    ^ theBrowser.

    "
     self openOnClass:Array againstVersion:'1.116'
     self openOnClass:Array againstVersion:nil            - against the version on which Array is based upon
     self openOnClass:Array againstVersion:#newest        - against the newest repository version

     self openOnClass:VersionDiffBrowser againstVersion:nil
     self openOnClass:VersionDiffBrowser againstVersion:#newest
    "
!

openOnClass:aClass labelA:aLabelA sourceA:aSourceA labelB:aLabelB sourceB:aSourceB 
"
create an VersionDiffBrowser instance and set the class change set of the
browser. The class diff set is generated from two source files.

<return: VersionDiffBrowser>
"
    ^ self
        openOnClass:aClass labelA:aLabelA sourceA:aSourceA labelB:aLabelB sourceB:aSourceB 
        title:nil
!

openOnClass:aClass labelA:aLabelA sourceA:aSourceA labelB:aLabelB sourceB:aSourceB title:ignoredTitle
"
create an VersionDiffBrowser instance and set the class change set of the
browser. The class diff set is generated from two source files.

<return: VersionDiffBrowser>
"
    |theBrowser|

    theBrowser := self new.
    theBrowser allButOpen.
    theBrowser setupForClass:aClass labelA:aLabelA sourceA:aSourceA labelB:aLabelB sourceB:aSourceB.
    theBrowser openWindow.
    ^ theBrowser.
!

openOnClass:aClass versionA:aVersionA versionB:aVersionB
"
create an VersionDiffBrowser instance and set the class change set of the
browser. The class diff set is generated from two class versions via
the source code manager .

<return: VersionDiffBrowser>
"
    |theBrowser|

    theBrowser := self new.
    theBrowser allButOpen.
    theBrowser setupForClass:aClass versionA:aVersionA versionB:aVersionB.
    theBrowser openWindow.
    ^ theBrowser.

    "
     self openOnClass:Array versionA:'1.116' versionB:'1.113'
    "
! !

!VersionDiffBrowser methodsFor:'accessing'!

changeSetA
"
gets the change set which contains only the new methods
in versionA of the class

<return: ChangeSet>
"

    ^ self classChangeSet methodsOnlyInA
!

changeSetB
"
gets the change set which contains only the new methods
in versionB of the class

<return: ChangeSet>
"

    ^ self classChangeSet methodsOnlyInB
!

classBeingCompared
"
gets the class from the change set which is compared.

<return: Class>
" 
    ^ self classChangeSet classBeingCompared
!

classChangeSet
"
returns the class change set which is the model of the version diff browser.

<return: ClassChangeSet> 
" 
    ^ classChangeSet
!

classChangeSet:aClassChangeSet
"
sets the class change set which is the model of the version diff browser.
The labels, the lists, the visibility and the selection of the lists must be reseted
when a new change set is given.

<return: self>
" 
    classChangeSet := aClassChangeSet.
    self updateLabels.
    self updateLists.
    self resetSelectionHolders.
!

selectedChangeInA
"
gets the selected method change for the 'method only in version A'.

<return: MethodChange | nil>
"
    |sel change|

    sel := self methodsOnlyInASelection value.
    sel notNil ifTrue:[
        change := (self changeSetA at:sel).
        ^ change
    ].
    ^ nil
!

selectedChangeInB
"
gets the selected method change for the 'method only in version B'.

<return: MethodChange | nil>
"
    |sel change|

    sel := self methodsOnlyInBSelection value.
    sel notNil ifTrue:[
        change := (self changeSetB at:sel).
        ^ change
    ].
    ^ nil
!

selectedChangesInM
"
get the two method changes for the selected 'changed method'.

<return: <Array with:MethodChange with:MethodChange | nil>>
"
    |sel changeA changeB|

    sel := self methodsChangedSelection value.
    sel notNil ifTrue:[
        changeA := (self classChangeSet methodsChanged at:sel) first.
        changeB := (self classChangeSet methodsChanged at:sel) second.
        ^ Array with:changeA 
                with:changeB 
    ].
    ^ nil
!

versionA
"
gets the first class version to be compared

<return: String>
"
    ^ self classChangeSet labelA

!

versionB
"
gets the second class version to be compared

<return: String>
"
    ^ self classChangeSet labelB

! !

!VersionDiffBrowser methodsFor:'actions'!

methodsChangedSelectionChanged
"
the selection in the list of the 'changed methods' changed.
Reset the selection of the two other lists and calc the method change.

<return: self>
"
    |sel idxA idxB changeA changeB methodsChanged|

    sel := self methodsChangedSelection value.
    sel notNil ifTrue:[
        self methodsOnlyInASelection value:nil.
        self methodsOnlyInBSelection value:nil.
        methodsChanged := self classChangeSet methodsChanged.
        changeA := (methodsChanged at:sel) first.
        changeB := (methodsChanged at:sel) second.
        self withReadCursorDo:[
            self diffTextView text1:changeA source text2:changeB source.
            self diffTextView moveToNextChanged
        ].
        self showDiffTextView.
    ]

    "Modified: / 3.9.1999 / 15:01:30 / ps"
!

methodsOnlyInASelectionChanged
"
the selection in the list of the 'method only in version A' changed.
Reset the selection of the two other lists and show the method in
the text view.

<return: self>
"
    |change sel|

    sel := self methodsOnlyInASelection value.
    sel notNil ifTrue:[
        self methodsOnlyInBSelection value:nil.
        self methodsChangedSelection value:nil.
        change := self changeSetA at:sel.
        self methodText value:(change source).    
        self showSingleTextView.
    ]
!

methodsOnlyInBSelectionChanged
"
the selection in the list of the 'method only in version B' changed.
Reset the selection of the two other lists and show the method in
the text view.

<return: self>
"
    |change sel|

    sel := self methodsOnlyInBSelection value.
    sel notNil ifTrue:[
        self methodsOnlyInASelection value:nil.
        self methodsChangedSelection value:nil.
        change := self changeSetB at:sel.
        self methodText value:(change source).    
        self showSingleTextView.
    ]
! !

!VersionDiffBrowser methodsFor:'aspects'!

boxAVisible
    |holder|

    (holder := builder bindingAt:#boxAVisible) isNil ifTrue:[
        builder aspectAt:#boxAVisible put:(holder := true asValue).
    ].
    ^ holder.
!

boxBVisible
    |holder|

    (holder := builder bindingAt:#boxBVisible) isNil ifTrue:[
        builder aspectAt:#boxBVisible put:(holder := true asValue).
    ].
    ^ holder.
!

boxMVisible
    |holder|

    (holder := builder bindingAt:#boxMVisible) isNil ifTrue:[
        builder aspectAt:#boxMVisible put:(holder := true asValue).
    ].
    ^ holder.
!

changedLabelHolder
"
aspect for the label of the changed method box.

<return: ValueHolder on: nil>
"
    |holder|

    (holder := builder bindingAt:#changedLabelHolder) isNil ifTrue:[
        builder aspectAt:#changedLabelHolder put:(holder :=  ValueHolder new).
    ].
    ^ holder.
!

diffTextLabelA
"
aspect for the label for version A.

<return: ValueHolder on: nil>
"

    |holder|

    (holder := builder bindingAt:#diffTextLabelA) isNil ifTrue:[
        builder aspectAt:#diffTextLabelA put:(holder :=  ValueHolder new).
    ].
    ^ holder.
!

diffTextLabelB
"
aspect for the label for version B.

<return: ValueHolder on: nil>
"

    |holder|

    (holder := builder bindingAt:#diffTextLabelB) isNil ifTrue:[
        builder aspectAt:#diffTextLabelB put:(holder :=  ValueHolder new).
    ].
    ^ holder.
!

diffTextView
"
return the component for the diff text view.

<return: HVScrollableView>
"
    diffTextView isNil ifTrue:[
        diffTextView := HVScrollableView
                           for:DiffTextView
                           miniScrollerH:true miniScrollerV:false.
        diffTextView addNextPreviousButtons.
    ].
    ^ diffTextView
!

methodText
"
aspect for the text in the method text view.

<return: ValueHolder on: nil>
"
    |holder|

    (holder := builder bindingAt:#methodText) isNil ifTrue:[
        builder aspectAt:#methodText put:(holder :=  ValueHolder new).
    ].
    ^ holder.
!

methodsChanged
"
aspect for the 'changed method' list.

<return: List>
"

    |holder|

    (holder := builder bindingAt:#methodsChanged) isNil ifTrue:[
        builder aspectAt:#methodsChanged put:(holder :=  List new).
    ].
    ^ holder.
!

methodsChangedSelection
"
aspect for the selection holder of 'changed method'-list.

<return: ValueHolder on: nil>
"

    |holder|

    (holder := builder bindingAt:#methodsChangedSelection) isNil ifTrue:[
        builder aspectAt:#methodsChangedSelection put:(holder :=  ValueHolder new).
    ].
    ^ holder.
!

methodsOnlyInA
"
aspect for the 'method only in version A' list.

<return: List>
"

    |holder|

    (holder := builder bindingAt:#methodsOnlyInA) isNil ifTrue:[
        builder aspectAt:#methodsOnlyInA put:(holder :=  List new).
    ].
    ^ holder.
!

methodsOnlyInASelection
"
aspect for the selection holder of 'method only in version A'-list.

<return: ValueHolder on: nil>
"

    |holder|

    (holder := builder bindingAt:#methodsOnlyInASelection) isNil ifTrue:[
        builder aspectAt:#methodsOnlyInASelection put:(holder :=  ValueHolder new).
    ].
    ^ holder.
!

methodsOnlyInB
"
aspect for the 'method only in version B' list.

<return: List>
"

    |holder|

    (holder := builder bindingAt:#methodsOnlyInB) isNil ifTrue:[
        builder aspectAt:#methodsOnlyInB put:(holder :=  List new).
    ].
    ^ holder.
!

methodsOnlyInBSelection
"
aspect for the selection holder of 'method only in version B'-list.

<return: ValueHolder on: nil>
"

    |holder|

    (holder := builder bindingAt:#methodsOnlyInBSelection) isNil ifTrue:[
        builder aspectAt:#methodsOnlyInBSelection put:(holder :=  ValueHolder new).
    ].
    ^ holder.
!

onlyInALabelHolder
"
aspect for the label for 'method only in version A'.

<return: ValueHolder on: nil>
"

    |holder|

    (holder := builder bindingAt:#onlyInALabelHolder) isNil ifTrue:[
        builder aspectAt:#onlyInALabelHolder put:(holder :=  ValueHolder new).
    ].
    ^ holder.
!

onlyInBLabelHolder
"
aspect for the label for 'method only in version B'.

<return: ValueHolder on: nil>
"

    |holder|

    (holder := builder bindingAt:#onlyInBLabelHolder) isNil ifTrue:[
        builder aspectAt:#onlyInBLabelHolder put:(holder :=  ValueHolder new).
    ].
    ^ holder.
! !

!VersionDiffBrowser methodsFor:'aspects-exported'!

changedLabelHolder:aValueHolder
    builder aspectAt:#changedLabelHolder put:aValueHolder.

!

classHolder:aValueHolder
    |holder|

    (holder := builder bindingAt:#classHolder) notNil ifTrue:[
        holder removeDependent:self
    ].
    builder aspectAt:#classHolder put:aValueHolder.
    aValueHolder notNil ifTrue:[
        aValueHolder addDependent:self
    ]
!

onlyInALabelHolder:aValueHolder
    builder aspectAt:#onlyInALabelHolder put:aValueHolder.
!

onlyInBLabelHolder:aValueHolder
    builder aspectAt:#onlyInBLabelHolder put:aValueHolder.
!

versionAHolder:aValueHolder
    |holder|

    (holder := builder bindingAt:#versionAHolder) notNil ifTrue:[
        holder removeDependent:self
    ].
    builder aspectAt:#versionAHolder put:aValueHolder.
    aValueHolder notNil ifTrue:[
        aValueHolder addDependent:self
    ]
!

versionBHolder:aValueHolder
    |holder|

    (holder := builder bindingAt:#versionBHolder) notNil ifTrue:[
        holder removeDependent:self
    ].
    builder aspectAt:#versionBHolder put:aValueHolder.
    aValueHolder notNil ifTrue:[
        aValueHolder addDependent:self
    ]
! !

!VersionDiffBrowser methodsFor:'change & update'!

update:something with:parameter from:changedObject
    |classHolder versionAHolder versionBHolder|

    classHolder := self classHolder.
    versionAHolder := self versionAHolder.
    versionBHolder := self versionBHolder.

    (changedObject == classHolder 
    or:[changedObject == versionAHolder
    or:[changedObject == versionBHolder]]) ifTrue:[
        self setupForClass:(classHolder value) versionA:(versionAHolder value) versionB:(versionBHolder value)
.        ^ self
    ].
    super update:something with:parameter from:changedObject
! !

!VersionDiffBrowser methodsFor:'initialization & release'!

postBuildWith:aBuilder
"
components which are invisible should be ignored by the panel.
Cannot be set via the interface builder.

<return: self>
"
    (aBuilder componentAt:#TopHorizontalPanel) ignoreInvisibleComponents:true.
    ^ super postBuildWith:aBuilder
! !

!VersionDiffBrowser methodsFor:'menu action'!

browseChange:aChange
"
browse the change in aChange 

<return: self>
"
    |cls sel m|

    aChange isNil ifTrue:[^ self].
    cls := aChange changeClass.
    sel := aChange changeSelector.
    (cls compiledMethodAt:sel) isNil ifTrue:[
        self information:'Method does not exist.'
    ].

    UserPreferences current systemBrowserClass openInClass:cls selector:sel.
!

browseClassInA
"
browse the selected method.

<return: self>
"
    self browseChange:(self selectedChangeInA).
!

browseClassInB
"
browse the selected method.

<return: self>
"
    self browseChange:(self selectedChangeInB).
!

browseClassInM
"
browse the selected method.

<return: self>
"
    self browseChange:(self selectedChangesInM first).
!

createPatchFile
    "create a pacthFile, to patch the old version (versionB) into
     the new version (versionA)"

    |oldVersion newVersion vsnMthdA vsnMthdB vsnA vsnB info|
    self halt.
    vsnMthdA := self changeSetA 
                detect:[:ch | ch isMethodChange
                              and:[ch changeClass isMeta
                              and:[ch changeSelector = #version]]]
                ifNone:nil.
    vsnMthdB := self changeSetA 
                detect:[:ch | ch isMethodChange
                              and:[ch changeClass isMeta
                              and:[ch changeSelector = #version]]]
                ifNone:nil.

    vsnMthdA notNil ifTrue:[
        "/ extract the version        
        vsnA := Class revisionStringFromSource:vsnMthdA source.
        vsnA notNil ifTrue:[
            info := Class revisionInfoFromString:vsnA.
            info notNil ifTrue:[
                vsnA := info at:#revision ifAbsent:nil.
            ] ifFalse:[
                vsnA := nil.
            ].
        ].
    ].
    vsnMthdB notNil ifTrue:[
        "/ extract the version        
        vsnB := Class revisionStringFromSource:vsnMthdB source.
        vsnB notNil ifTrue:[
            info := Class revisionInfoFromString:vsnB.
            info notNil ifTrue:[
                vsnB := info at:#revision ifAbsent:nil.
            ] ifFalse:[
                vsnB := nil.
            ].
        ].
    ].

    (vsnA isNil or:[vsnB isNil or:[vsnA = vsnB]]) ifTrue:[
        self warn:'The generated patch file will not be able to validate/update the class version'.
    ].

self halt.

!

inspectSelectedChangeInA
"
inspect the selected method change for the 'method only in version A'.

<return: self>
"
    self selectedChangeInA inspect.
!

inspectSelectedChangeInB
"
inspect the selected method change for the 'method only in version B'.

<return: self>
"
    self selectedChangeInB inspect.
!

inspectSelectedChangeInM
"
inspect the two method changes for the selected 'changed method'.

<return: self>
"
    |changes|

    changes := self selectedChangesInM.
    changes do:[:change |
        change inspect
    ].
!

mainMenu
"
if this application runs as an subapplication,
the menu bar should not be used.

<return: self>
"
    self masterApplication isNil ifTrue:[
        ^ self class mainMenu
    ].
    ^ nil
! !

!VersionDiffBrowser methodsFor:'private'!

generator
"
generate a print string for a change.

<return: Block>
"
    ^[:aChange |
        |result mPkg cPkg cls m changeClassName|

        aChange isClassDefinitionChange ifTrue:[
            result := aChange printStringWithoutClassName
        ] ifFalse:[
            aChange isDoIt ifTrue:[
                result := aChange source , ' (doIt)'
            ] ifFalse:[
                changeClassName := aChange className.
                changeClassName = self classBeingCompared name ifTrue:[
                    result := aChange printStringWithoutClassName.
                    aChange isMethodChange ifTrue:[
                        cls := aChange changeClass.
                        cls notNil ifTrue:[
                            m := cls compiledMethodAt:aChange selector.
                            (m notNil 
                            and:[m package ~= cls package]) ifTrue:[
                                result := aChange printStringWithoutClassName , ' [' , m package , ']'.
                            ].
                        ].
                    ]
                ] ifFalse:[
                    "/ include name in private class changes
                    (changeClassName startsWith:(self classBeingCompared name , '::')) ifTrue:[
                        result := (changeClassName copyFrom:(self classBeingCompared name size + 3)) , ' ' ,  aChange printStringWithoutClassName
                    ] ifFalse:[
                        result := aChange printString
                    ]
                ]
            ]
        ].
        result
    ].

!

resetSelectionHolders
"
reset all selection holders when a new change set is given.
First set the selection to nil. 

<return: self>
"

    self methodsChangedSelection value:nil.
    self methodsOnlyInASelection value:nil.
    self methodsOnlyInBSelection value:nil.
    self diffTextView text1:'' text2:''.
    self methodText value:''.

"/    self methodsChangedSelectionChanged.
"/    self methodsOnlyInASelectionChanged.
"/    self methodsOnlyInBSelectionChanged.

!

showDiffTextView
"
if a method change is selected, then show the diff text view.
Add an accept entry to the popup menu.

<return: self>
"
    |builder|

    builder := self builder.
    (builder componentAt:#diffTextViewBox) raise; beVisible.
    (builder componentAt:#singleTextView) beInvisible.
    (builder componentAt:#diffTextView) realizeAllSubViews.

    self addAcceptToTextViewMenus.
!

showSingleTextView
"
if a method  is selected which is only in version A or B of the class,
then show the text view.
Add an accept entry to the popup menu.

<return: self>
"

    |builder|

    builder := self builder.
    (builder componentAt:#singleTextView) raise; beVisible.
    (builder componentAt:#diffTextViewBox) beInvisible.
    self addAcceptToTextViewMenus.
!

updateLabels
"
update the labels of the diff text view. Show the version numbers
of the class.

<return: self>
"

    |theVersionA theVersionB builder|

    builder := self builder.
    (theVersionA := self versionA) notNil ifTrue:[
        self diffTextLabelA value:theVersionA.
        self onlyInALabelHolder value:('Only in ' , theVersionA).
        (builder componentAt:#OnlyInALabel) backgroundColor:Color green darkened.
    ].
    (theVersionB :=self versionB) notNil ifTrue:[
        self diffTextLabelB value:theVersionB.
        self onlyInBLabelHolder value:('Only in ' , theVersionB).
        (builder componentAt:#OnlyInBLabel) backgroundColor:Color red darkened.
    ].
!

updateLists
"walk over the two changeSets and add change to one of the tree lists.
Methods which belong to the version A of the class or version B go
to the list methodsOnlyInA or methodsOnlyInB.
Methods which are changed from version A to B go to the list methodsChanged.

<return: self>
"
    |classChangeSet|

    classChangeSet := self classChangeSet.

    self methodsOnlyInA 
        removeAll; addAll: (classChangeSet methodsOnlyInA collect:self generator).

    self methodsOnlyInB 
        removeAll; addAll: (classChangeSet methodsOnlyInB collect:self generator).

    self methodsChanged 
        removeAll; addAll: (classChangeSet methodsChanged collect:[:arr| self generator value:(arr first)]).

    self boxAVisible value:(self methodsOnlyInA notEmpty).
    self boxBVisible value:(self methodsOnlyInB notEmpty).

    self boxMVisible value:(self methodsChanged isEmpty 
                            and: [(self methodsOnlyInA notEmpty 
                                  or:[self methodsOnlyInB notEmpty])]) not.
! !

!VersionDiffBrowser methodsFor:'setup'!

setupForClass:aClass againstVersion:aVersionA
"
compute the class change set for the class aClass of its current version against the repository version A.
When setting the class change set, the labels, list etc. of the receiver
are updated.

<return: self>
"
    self classChangeSet:(ClassChangeSet newForClass:aClass againstVersion:aVersionA)
!

setupForClass:aClass labelA:aLabelA sourceA:aSourceA labelB:aLabelB sourceB:aSourceB
"
generate the class change set from the two source files A and B.
When setting the class change set, the labels, list etc. of the receiver
are updated.

<return: self>
"
    self classChangeSet:(ClassChangeSet newForClass:aClass labelA:aLabelA sourceA:aSourceA labelB:aLabelB sourceB:aSourceB)
!

setupForClass:aClass versionA:aVersionA versionB:aVersionB
"
compute the class change set for the class aClass and the versions A and B.
When setting the class change set, the labels, list etc. of the receiver
are updated.

<return: self>
"
    self classChangeSet:(ClassChangeSet newForClass:aClass versionA:aVersionA versionB:aVersionB)
! !

!VersionDiffBrowser methodsFor:'values'!

addAcceptToTextViewMenus
"
add to the standard diff text view or single text views menu an accept entry.
The acceptAction will fetch the corresponding change and apply it
(not the shown text)

<return: self>
"

    |m v diffTextView leftView rightView singleView|

    diffTextView := self diffTextView.
    leftView := diffTextView leftTextView.
    rightView := diffTextView rightTextView.
    singleView := self builder componentAt:#singleTextView.

    (Array 
        with:leftView
        with:rightView
        with:singleView)
    do:[:v |
        m := v editMenu.
        (m selectorAt:#accept) isNil ifTrue:[
            m addLabels:#('-' 'accept')
              selectors:(Array with:nil with:#accept)
              after:#copySelection.
        ].
        m 
            actionAt:#accept 
            put:[
                |idx idx2 text change changeSet|  

                changeSet := self classChangeSet.

                v == singleView ifTrue:[
                    self methodsOnlyInASelection value notNil
                    ifTrue: [
                        change := (changeSet methodsOnlyInA at: self methodsOnlyInASelection value)
                    ] ifFalse:[
                        change := (changeSet methodsOnlyInB at: self methodsOnlyInBSelection value)
                    ].
                ] ifFalse:[     
                     idx := self methodsChangedSelection value.

                     v == self diffTextView leftTextView ifTrue:[
                        change := (changeSet methodsChanged at:idx) first.
                     ] ifFalse:[
                        change := (changeSet methodsChanged at:idx) second.
                     ].
                ].
                change apply
            ].
        m selectorAt:#accept put:nil.
        v menuHolder:m.
        v menuMessage:#value.
    ]
! !

!VersionDiffBrowser::ClassChangeSet class methodsFor:'instance creation'!

changeSetForClass:aClass 
"return a ChangeSet for the class aClass and its current source.
The source is generated into a stream and then the change set is generated
from the source stream.

<return: ChangeSet|nil>
"
    |theChangeSet source s|

    s := '' writeStream.
    aClass fileOutOn:s.
    source := s contents.

    theChangeSet := ChangeSet fromStream:(source readStream).
    ^theChangeSet
!

changeSetForClass:aClass andRevision:aVersion
"return a ChangeSet for the class aClass and version aVersion.
The version from the class source stream is checked out from the repositiory
into a source stream. Then the change set is generated from the source stream.

<return: ChangeSet|nil>
"
    |theSourceCodeManager theSourceStream theChangeSet|

    theSourceCodeManager := aClass sourceCodeManager.
    [theSourceStream := theSourceCodeManager getSourceStreamFor:aClass revision:aVersion.
    theSourceStream notNil
        ifTrue:[theChangeSet:=ChangeSet fromStream:theSourceStream]]
    valueNowOrOnUnwindDo:
        [theSourceStream notNil ifTrue:[theSourceStream close]].
    ^theChangeSet
!

changeSetForClass:aClass andSource:aSource 
"return a ChangeSet for the class aClass and source aSource.
The source is converted to a stream and then the change set is generated
from the source stream.

<return: ChangeSet|nil>
"
    |theChangeSet theSourceStream|

    [(theSourceStream := aSource readStream) notNil
        ifTrue:[theChangeSet:=ChangeSet fromStream:(theSourceStream := aSource readStream)]]
    valueNowOrOnUnwindDo:
        [theSourceStream notNil
            ifTrue:[theSourceStream close]].
    ^theChangeSet
!

newForClass:aClass againstVersion:aVersionA
"return a ClassChangeSet for the class aClass against some verison in the repository.
A new instance of ClassChangeSet is generated containing 
the correspondenting changes.

<return: ClassChangeSet>
"
    |theChangeSetA theChangeSetB theClassChangeSet versionCompared diffSet|

    theClassChangeSet := self new.
    theClassChangeSet classBeingCompared:aClass.
    versionCompared := aVersionA.
    aVersionA isNil ifTrue:[
        theClassChangeSet labelA:(versionCompared := aClass revision).
    ] ifFalse:[
        aVersionA == #newest ifTrue:[
            theClassChangeSet labelA:'newest'.
        ] ifFalse:[
            theClassChangeSet labelA:versionCompared.
        ]
    ].
    theClassChangeSet labelB:'current'.
    theChangeSetA := self changeSetForClass: aClass andRevision: versionCompared.
    theChangeSetB := self changeSetForClass: aClass.
    theChangeSetA isNil
        ifTrue: [theChangeSetA := ChangeSet new].
    theChangeSetB isNil
        ifTrue: [theChangeSetB := ChangeSet new].
    "/ if we are comparing a private class, prune out other changes
    aClass isPrivate ifTrue:[
        theChangeSetA removeAllSuchThat:[:aChange |
                                                aChange className ~= aClass name
                                        ].
        theChangeSetB removeAllSuchThat:[:aChange |
                                                aChange className ~= aClass name
                                        ].
    ].
    self activityNotification:'generating diff-set...'.
    diffSet := theClassChangeSet diffSet:(theChangeSetA diffSetsAgainst:theChangeSetB).
    self activityNotification:nil.
    ^ diffSet
!

newForClass:aClass labelA:aLabelA sourceA:aSourceA labelB:aLabelB sourceB:aSourceB
"return a ClassChangeSet for the class aClass and the two sources.
The two classes are compared via the source files. A new instance of 
ClassChangeSet is generated containing the correspondenting changes.

<return: ClassChangeSet>
"
    |theChangeSetA theChangeSetB theClassChangeSet|

    theClassChangeSet := self new.
    theClassChangeSet classBeingCompared:aClass.
    theClassChangeSet labelA:aLabelA.
    theClassChangeSet labelB:aLabelB.
    theChangeSetA:=self changeSetForClass:aClass andSource:aSourceA. 
    theChangeSetB:=self changeSetForClass:aClass andSource:aSourceB.
    theChangeSetA isNil
        ifTrue: [theChangeSetA := ChangeSet new].
    theChangeSetB isNil
        ifTrue: [theChangeSetB := ChangeSet new].
    ^theClassChangeSet diffSet:(theChangeSetA diffSetsAgainst:theChangeSetB)
!

newForClass:aClass versionA:aVersionA versionB:aVersionB
"return a ClassChangeSet for the class aClass and the two versions.
The two class version are checked out from the repository and then being
compared. A new instance of ClassChangeSet is generated containing 
the correspondenting changes.

<return: ClassChangeSet>
"
    |theChangeSetA theChangeSetB theClassChangeSet|

    theClassChangeSet := self new.
    theClassChangeSet classBeingCompared:aClass.
    theClassChangeSet labelA:aVersionA.
    theClassChangeSet labelB:aVersionB.
    theChangeSetA := self changeSetForClass: aClass andRevision: aVersionA.
    theChangeSetB := self changeSetForClass: aClass andRevision: aVersionB.
    theChangeSetA isNil
        ifTrue: [theChangeSetA := ChangeSet new].
    theChangeSetB isNil
        ifTrue: [theChangeSetB := ChangeSet new].
    ^theClassChangeSet diffSet:(theChangeSetA diffSetsAgainst:theChangeSetB)

! !

!VersionDiffBrowser::ClassChangeSet methodsFor:'accessing'!

classBeingCompared
"returns the value of the class which is compared

<return: Behavior>
"

    ^ classBeingCompared
!

classBeingCompared:something
"set the value of the class which is compared

<return: self>"

    classBeingCompared := something.
!

diffSet
"returns a identity dictionary containing the different changes.
key:            value:
#changed        OrderedCollection of arrays containing ChangeSets for methods 
                which are changed and exists in the class versionA and versionB
#onlyInArg      ChangeSet for the methods which exists only in the class versionB
#onlyInReceiver ChangeSet for the methods which exists only in the class versionA

<return: IdentityDictionary>
"  
    ^ diffSet
!

diffSet:something
"sets a identity dictionary containing the different changes.
key:            value:
#changed        OrderedCollection of arrays containing ChangeSets for methods 
                which are changed and exists in the class versionA and versionB
#onlyInArg      ChangeSet for the methods which exists only in the class versionB
#onlyInReceiver ChangeSet for the methods which exists only in the class versionA

<return: self>
"  

    diffSet := something.
!

labelA
"returns the label for the class versionA

<return: String>
"
    ^ labelA
!

labelA:something
"sets the label for the class versionA

<return: self>
"
    labelA := something.
!

labelB
"returns the label for the class versionB

<return: String>
"
    ^ labelB
!

labelB:something
"sets the label for the class versionB

<return: self>
"

    labelB := something.
!

methodsChanged
"returns a  OrderedCollection of arrays containing ChangeSets for methods 
which are changed and exists in the class versionA and versionB

<return: OrderedCollection>
"
    ^ self diffSet at:#changed
!

methodsOnlyInA
"returns a ChangeSet for the methods which exists only in the class versionA

<return: ChangeSet>
"  

    ^ self diffSet at:#onlyInReceiver
!

methodsOnlyInB
"returns a ChangeSet for the methods which exists only in the class versionB

<return: ChangeSet>
"  

    ^ self diffSet at:#onlyInArg
! !

!VersionDiffBrowser class methodsFor:'documentation'!

version
    ^ '$Header: /cvs/stx/stx/libtool/VersionDiffBrowser.st,v 1.30 2000-08-22 10:55:30 cg Exp $'
! !