ChangesBrowser.st
author Claus Gittinger <cg@exept.de>
Fri, 23 Apr 1999 14:27:38 +0200
changeset 2114 720edbe757f5
parent 2093 5eb2123927b9
child 2132 4b6c57b84a10
permissions -rw-r--r--
category change

"
 COPYRIGHT (c) 1990 by Claus Gittinger
	      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.
"

StandardSystemView subclass:#ChangesBrowser
	instanceVariableNames:'changeListView codeView changeFileName changeChunks
		changePositions changeClassNames changeHeaderLines
		changeIsFollowupMethodChange anyChanges changeNrShown
		changeNrProcessed skipSignal compareChanges compareCheckBox
		changeFileSize changeFileTimestamp checkBlock changeTimeStamps
		tabSpec autoUpdate editingClassSource lastSearchType'
	classVariableNames:'CompressSnapshotInfo'
	poolDictionaries:''
	category:'Interface-Browsers'
!

!ChangesBrowser class methodsFor:'documentation'!

copyright
"
 COPYRIGHT (c) 1990 by Claus Gittinger
	      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 implements a browser for the changes-file.
    See the extra document 'doc/misc/cbrowser.doc' for how to use this browser.

    written jan 90 by claus

    [Class variables:]
	CompressSnapshotInfo            if true (the default), snapshot entries
					are also compressed in the compress function.
					Some users prefer them to be not compressed.
					Set it to false for this.

    Notice:
	this needs a total rewrite, to build up a changeSet from the file
	(which did not exist when the ChangesBrowser was originally written) 
	and manipulate that changeSet.

	This way, we get a browser for any upcoming incore changeSets for
	free. Also, this will put the chunk analyzation code into Change and
	subclasses (where it belongs) and give a better encapsulation and
	overall structure. Do not take this as an example for good style ;-)

    [author:]
	Claus Gittinger

    [start with:]
	ChangesBrowser open

    [see also:]
	( Using the ChangesBrowser :html: tools/cbrowser/TOP.html )
        
"
! !

!ChangesBrowser class methodsFor:'instance creation'!

new
    "create a new changes browser"

    ^ super 
        label:(self defaultLabel)
        icon:(self defaultIcon)

    "Modified: / 6.2.1998 / 13:25:18 / cg"
!

openOn:aFileName
    "create c changes browser on a change file"

    ^ ((self new label:(self defaultLabel , ': ', aFileName)) 
        changeFileName:aFileName) open

    "Modified: / 6.2.1998 / 13:27:19 / cg"
! !

!ChangesBrowser class methodsFor:'behavior'!

autoSelectNext
    "returning true here, makes a Delete operation automatically
     select the next change"

    ^ true
! !

!ChangesBrowser class methodsFor:'defaults'!

defaultIcon
    "return the browsers default window icon"

    <resource: #style (#ICON #ICON_FILE)>

    |nm i|

    (i := DefaultIcon) isNil ifTrue:[
	i := self classResources at:'ICON' default:nil.
	i isNil ifTrue:[
	    nm := ClassResources at:'ICON_FILE' default:'CBrowser.xbm'.
	    i := Image fromFile:nm resolution:100.
	    i isNil ifTrue:[
		i := Image fromFile:('bitmaps/' , nm) resolution:100.
		i isNil ifTrue:[
		    i := StandardSystemView defaultIcon
		]
	    ]
	].
	i notNil ifTrue:[
	    DefaultIcon := i := i on:Display
	]
    ].
    ^ i

    "Modified: 19.3.1997 / 20:48:34 / ca"
    "Modified: 18.4.1997 / 15:16:24 / cg"
!

defaultLabel
    ^ 'Changes Browser'
! !

!ChangesBrowser methodsFor:'compiler interface'!

wantChangeLog
    "sent by the compiler to ask if a changeLog entry should
     be written. Return false here."

    ^ false
! !

!ChangesBrowser methodsFor:'error handling'!

correctableError:aString position:relPos to:relEndPos from:aCompiler
    "compiler notifys us of an error - this should really not happen since
     changes ought to be correct (did someone edit the changes file ??).
     Show the bad change in the codeView and let codeView hilight the error;
     no corrections allowed here therefore return false"

    self error:aString position:relPos to:relEndPos from:aCompiler.
    ^ false
!

error:aString position:relPos to:relEndPos from:aCompiler
    "compiler notifys us of an error - this should really not happen since
     changes ought to be correct (did someone edit the changes file ??).
     Show the bad change in the codeView and let codeView hilight the error"

    |action|

    (changeNrProcessed ~~ changeNrShown) ifTrue:[
	self changeSelection:changeNrProcessed
    ].
    (skipSignal notNil) ifTrue:[

	codeView highlightingErrorPosition:relPos to:relEndPos do:[
	    |box|

	    "
	     start dialog - make certain cleanup is done
	    "
	    action := OptionBox 
			  request:aString
			  label:'Error'
			  form:(WarningBox iconBitmap)
			  buttonLabels:#('cancel' 'skip' 'continue')
			  values:#(#abort #skip #continue)
			  default:#continue.
	].

	action == #abort ifTrue:[
	    Object abortSignal raise.
	    ^ false
	].
	action == #skip ifTrue:[
	    skipSignal raise.
	    ^ false
	].
	^  false 
    ].
    ^ codeView error:aString position:relPos to:relEndPos from:aCompiler

    "Modified: 20.2.1996 / 20:47:59 / cg"
!

warning:aString position:relPos to:relEndPos from:aCompiler
    "compiler notifys us of a warning - ignore it"

    ^ self
! !

!ChangesBrowser methodsFor:'event handling '!

handlesKeyPress:key inView:view
    "this method is reached via delegation: are we prepared to handle
     a keyPress in some other view ?"

    <resource: #keyboard (#Delete #BackSpace #Accept #Find #FindPrev #FindNext)>

    view == changeListView ifTrue:[
	(key == #Delete 
	or:[key == #BackSpace
	or:[key == #Accept
	or:[key == #Find
	or:[key == #FindPrev
	or:[key == #FindNext]]]]]) ifTrue:[^ true].
    ].
    ^ false

    "Modified: 8.4.1997 / 11:01:42 / cg"
!

keyPress:key x:x y:y view:view
    "this method is reached via delegation from the changeListView"

    <resource: #keyboard (#Delete #BackSpace #Accept #Find #FindPrev #FindNext)>

    (key == #Delete or:[key == #BackSpace]) ifTrue:[
        self doDelete.
        ^ self
    ].
    (key == #Accept) ifTrue:[
        self doApply.
        ^ self
    ].
    (key == #Find) ifTrue:[
        self findClass.
        ^ self
    ].
    (key == #FindPrev) ifTrue:[
        self findPrevious.
        ^ self
    ].
    (key == #FindNext) ifTrue:[
        self findNext.
        ^ self
    ].
    changeListView keyPress:key x:x y:y

    "Modified: / 18.6.1998 / 22:15:36 / cg"
! !

!ChangesBrowser methodsFor:'help '!

helpTextFor:aComponent
    |s|

    aComponent == codeView ifTrue:[
	s := 'HELP_CODEVIEW'
    ].
    aComponent == changeListView ifTrue:[
	s := 'HELP_CHANGELIST'
    ].
    ((aComponent == compareCheckBox)
    or:[aComponent superView == compareCheckBox]) ifTrue:[
	s := 'HELP_CHECK_COMPARE'
    ].
    s notNil ifTrue:[
	^ resources string:s
    ].
    ^ nil
!

showActivity:someMessage
    "some activityNotification to be forwarded to the user;
     show it in the windows title area here."

    self label:someMessage

    "Created: 24.2.1996 / 19:35:42 / cg"
    "Modified: 23.4.1996 / 21:39:36 / cg"
! !

!ChangesBrowser methodsFor:'initialize / release'!

changeListMenu
    <resource: #keyboard ( #Accept #Delete ) >
    <resource: #programMenu >

    |items m|

    self sensor ctrlDown ifTrue:[
        lastSearchType == #selector ifTrue:[
            items := #(
                              ('search class'                 findClass             #Find         )
                              ('next for this class'          findNextForClass                    )
                              ('previous for this class'      findPreviousForClass                )
                              ('-'                                                                )
                              ('search selector'              findSelector                        )
                              ('next with this selector'      findNextForSelector     #FindNext   )
                              ('previous with this selector'  findPreviousForSelector #FindPrev   )
                              ('-'                                                                )
                              ('next snapshot'                findNextSnapshot                    )
                              ('previous snapshot'            findPreviousSnapshot                )
                     ).
        ] ifFalse:[
            items := #(
                              ('search class'                 findClass             #Find         )
                              ('next for this class'          findNextForClass      #FindNext     )
                              ('previous for this class'      findPreviousForClass  #FindPrev     )
                              ('-'                                                                )
                              ('search selector'              findSelector                        )
                              ('next with this selector'      findNextForSelector                 )
                              ('previous with this selector'  findPreviousForSelector             )
                              ('-'                                                                )
                              ('next snapshot'                findNextSnapshot                    )
                              ('previous snapshot'            findPreviousSnapshot                )
                     ).
        ].
        ^ PopUpMenu itemList:items resources:resources.
    ].

    items := #(
                      ('apply'                        doApply                    Accept)
                      ('apply to end'                 doApplyRest                      )
                      ('apply for class to end'       doApplyClassRest                 )
                      ('apply all'                    doApplyAll                       )
                      ('-'                                                             )
                      ('delete'                       doDelete                   Delete)
                      ('delete to end'                doDeleteRest                     )
                      ('delete for class to end'      doDeleteClassRest                )
                      ('delete for class from begin'  doDeleteClassFromBeginning       )
                      ('delete all for class'         doDeleteClassAll                 )
                      ('-'                                                             )
                      ('compress'                     doCompress                       )
                      ('compress for class'           doCompressClass                  )
                      ('compare with current version' doCompare                        )
                      ('browse'                       doBrowse                         )
                      ('-'                                                             )
                      ('make change a patch'          doMakePatch                      )
             ).

    editingClassSource ifFalse:[
        items := items , #(
                      ('fileout & delete all for class' doFileoutAndDeleteClassAll     )
                          )
    ].

    items := items , #(
                      ('-'                                             )
                      ('save in ...'                  doSave           )
                      ('save to end in ...'           doSaveRest       )
                      ('save for class to end in ...' doSaveClassRest  )
                      ('save all for class in ...'    doSaveClassAll   )
                      ('-'                                             )
                     ).

    editingClassSource ifTrue:[
        items := items , #(
                      ('writeback classFile'  doWriteBack )
                     )
    ] ifFalse:[
        items := items , #(
                      ('writeback changeFile' doWriteBack )
                     )
    ].

    items := items , #(
                  ('-'                                                             )
                  ('update view'                  doUpdate                         )
                 ).

    m := PopUpMenu itemList:items resources:resources.

    "/
    "/ disable those that require a selected entry
    "/
    changeListView hasSelection ifFalse:[
        m disableAll:#(doApply doApplyClassRest doApplyRest doDelete doDeleteRest doDeleteClassRest
                       doDeleteClassFromBeginning doDeleteClassAll 
                       doCompare doCompressClass doMakePatch doSaveChangeInFile doMakePermanent
                       doSave doSaveRest doSaveClassAll doSaveClassRest doBrowse doFileoutAndDeleteClassAll) 
    ] ifTrue:[
        (self classNameOfChange:(changeListView selection)) isNil ifTrue:[
            m disableAll:#(doApplyClassRest doDeleteClassRest
                           doDeleteClassFromBeginning doDeleteClassAll 
                           doCompressClass
                           doSaveClassAll doSaveClassRest doBrowse doFileoutAndDeleteClassAll) 
        ]
    ].

    "/
    "/ disable those that do not make sense with autoUpdate
    "/ ('cause this would be overwritten by next update operation)
    "/

    autoUpdate ifTrue:[
        m disableAll:#(doDelete doDeleteRest doDeleteClassRest doDeleteClassAll doCompress 
                       doFileoutAndDeleteClassAll doWriteBack)
    ].
    ^ m

    "Modified: / 6.9.1995 / 17:14:22 / claus"
    "Modified: / 22.8.1998 / 15:50:17 / cg"
!

compare:aBoolean
    "sent from the compare-toggle"

    aBoolean ~~ compareChanges ifTrue:[
	compareChanges := aBoolean.
	compareChanges ifTrue:[
	    tabSpec positions:#(0  0.15  5   8.5 ).
	    self doUpdate
	] ifFalse:[
	    "/
	    "/ set tabs to hide compare-column
	    "/
	    tabSpec positions:#(-1  0    5   8.5 ).
	    changeListView invalidate. "/ clear; redraw.
	]
    ]

    "Modified: 29.5.1996 / 16:12:19 / cg"
!

destroy
    "destroy the receiver; make certain, that boxes are destroyed too"

    Processor removeTimedBlock:checkBlock.
    ObjectMemory removeDependent:self.
    super destroy
!

focusSequence
    ^ Array with:changeListView with:codeView
!

initialize
    |panel v upperFrame buttonPanel autoUpdateCheckBox|

    super initialize.

    changeFileName := ObjectMemory nameForChanges. "/ 'changes'.
    compareChanges := false.
    autoUpdate := false.

    "
      checkBlock is executed by the Processor.
      We use #pushEvent: to perform the update
      in our windowgroups process.
    "
    checkBlock := [self pushEvent:#checkIfFileHasChanged].

    panel := VariableVerticalPanel origin:(0.0 @ 0.0)
                                   corner:(1.0 @ 1.0)
                              borderWidth:0
                                       in:self.

    upperFrame := View origin:(0.0 @ 0.0) corner:(1.0 @ 0.3) in:panel.

    v := HVScrollableView for:SelectionInListView miniScrollerH:true in:upperFrame.
    v origin:(0.0 @ 0.0) corner:(0.8 @ 1.0).
    changeListView := v scrolledView.
    changeListView delegate:self.
    changeListView menuHolder:self; menuPerformer:self; menuMessage:#changeListMenu.
    changeListView doubleClickAction:[:line | self doubleClickOnChange:line].

    buttonPanel := VerticalPanelView in:upperFrame.
    buttonPanel origin:(0.8 @ 0.0) corner:(1.0 @ 1.0).
    buttonPanel verticalLayout:#topSpace; horizontalLayout:#leftSpace.

    compareCheckBox := CheckBox new.
    compareCheckBox label:(resources string:'compare').
    compareCheckBox action:[:val | self compare:val].
    buttonPanel addSubView:compareCheckBox.

    autoUpdateCheckBox := CheckBox new.
    autoUpdateCheckBox label:(resources string:'auto update').
    autoUpdateCheckBox action:[:val | self autoUpdate:val].
    buttonPanel addSubView:autoUpdateCheckBox.

    v := HVScrollableView for:CodeView miniScrollerH:true miniScrollerV:false in:panel.
    v origin:(0.0 @ 0.3) corner:(1.0 @ 1.0).
    codeView := v scrolledView.
    codeView readOnly:true.

    anyChanges := false.
    ObjectMemory addDependent:self.   "to get shutdown-update"

    tabSpec := TabulatorSpecification new.
    tabSpec unit:#inch.
    tabSpec positions:#(-1      0        5      8.5 ).
    "                   +/-    cls>>sel  type   info"
    tabSpec align:    #(#left  #left     #left  #left).

    "Modified: / 27.3.1997 / 11:07:07 / stefan"
    "Modified: / 6.2.1998 / 13:08:16 / cg"
!

postRealize
    self readChangesFileInBackground:true.
    self setChangeList.
    changeListView action:[:lineNr | self changeSelection:lineNr].
    Processor addTimedBlock:checkBlock afterSeconds:5.

    "Created: 24.7.1997 / 18:06:12 / cg"
!

update:what with:aParameter from:changedObject
    |box|

    (what == #aboutToExit) ifTrue:[
	"
	 smalltalk is about to shut down -
	 - if change list was modified, ask user and save if requested.
	"
	anyChanges ifTrue:[
	    self raiseDeiconified.

	    box := YesNoBox new.
	    box title:(resources at:'changefile has not been updated from the modified changelist.\\Update before exiting ?') withCRs.
	    box okText:(resources at:'update') noText:(resources at:'don''t update').
	    box yesAction:[self writeBackChanges] 
		 noAction:[].
	    box showAtPointer.
	    box destroy
	].
	^ self
    ].
    super update:what

    "Created: 15.6.1996 / 15:26:30 / cg"
    "Modified: 7.1.1997 / 23:03:47 / cg"
! !

!ChangesBrowser methodsFor:'private'!

appendChange:changeNr toFile:fileName
    "append change to a file. return true if ok."

    |aStream outStream chunk sawExcla separator|

    outStream := FileStream oldFileNamed:fileName.
    outStream isNil ifTrue:[
        outStream isNil ifTrue:[
            outStream := FileStream newFileNamed:fileName.
            outStream isNil ifTrue:[
                self warn:'cannot update file ''%1''' with:fileName.
                ^ false
            ]
        ]
    ].

    outStream setToEnd.

    aStream := self streamForChange:changeNr.
    aStream isNil ifTrue:[
        self warn:'oops cannot read change'.
        ^ false
    ].

    separator := aStream class chunkSeparator.

    (self changeIsFollowupMethodChange:changeNr) ifTrue:[
        sawExcla := true.
        chunk := changeChunks at:changeNr
    ] ifFalse:[
        sawExcla := aStream peekFor:separator.
        chunk := aStream nextChunk.
    ].
    sawExcla ifTrue:[
        outStream nextPut:separator
    ].
    outStream nextChunkPut:chunk; cr.
    sawExcla ifTrue:[
        chunk := aStream nextChunk.
        outStream nextChunkPut:chunk; space
    ].
    sawExcla ifTrue:[
        outStream nextPut:separator
    ].
    outStream cr.
    aStream close.
    outStream close.
    ^ true

    "Modified: / 6.2.1998 / 13:03:54 / cg"
!

applyChange:changeNr
    "fileIn a change"

    |aStream nm applyAction|

    aStream := self streamForChange:changeNr.
    aStream isNil ifTrue:[^ self].

    nm := self classNameOfChange:changeNr.
    nm notNil ifTrue:[
        |cls|

        cls := Smalltalk at:(nm asSymbol) ifAbsent:[].
        cls notNil ifTrue:[
            cls isLoaded ifFalse:[
                cls autoload
            ]
        ]
    ].

    changeNrProcessed := changeNr.

    applyAction := [
        |sig|

        (skipSignal notNil) ifTrue:[
            sig := skipSignal
        ] ifFalse:[
            sig := Object abortSignal
        ].
        sig catch:[
            |reader doItChunk methodsForChunk|

            "/ a followup methodsFor: chunk ...
            (self changeIsFollowupMethodChange:changeNr) ifTrue:[
                methodsForChunk := (changeChunks at:changeNr).
            ] 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:[
                reader := Compiler evaluate:methodsForChunk notifying:self.
                reader fileInFrom:aStream notifying:self passChunk:false single:true.
            ]
        ].
        changeNrProcessed := nil.
    ].

    "/
    "/ if I am showing the changes file, dont update it
    "/
    changeFileName = ObjectMemory nameForChanges ifTrue:[
        Class withoutUpdatingChangesDo:applyAction
    ] ifFalse:[
        applyAction value
    ].
    aStream close

    "Modified: / 7.2.1998 / 19:56:34 / cg"
!

autoSelect:changeNr
    "select a change"

    self class autoSelectNext ifTrue:[
        (changeNr <= self numberOfChanges) ifTrue:[
            changeListView setSelection:changeNr.
            self changeSelection:changeNr.
            ^ self
        ]
    ].
    self clearCodeView.
    changeListView setSelection:nil.

    "Modified: / 18.5.1998 / 14:26:43 / cg"
!

autoSelectLast
    "select the last change"

    self autoSelect:(self numberOfChanges)
!

autoSelectOrEnd:changeNr
    "select the next change or the last"

    |last|

    last := self numberOfChanges.
    changeNr < last ifTrue:[
	self autoSelect:changeNr
    ] ifFalse:[
	changeListView setSelection:last .
	self changeSelection:last.
    ]

    "Modified: 25.5.1996 / 12:26:17 / cg"
!

changeFileName:aFileName
    changeFileName := aFileName
!

changeIsFollowupMethodChange:changeNr
    ^ changeIsFollowupMethodChange at:changeNr

    "Created: / 6.2.1998 / 13:03:39 / cg"
!

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

    "Created: 12.12.1995 / 14:04:39 / cg"
    "Modified: 12.12.1995 / 14:11:05 / cg"
!

checkIfFileHasChanged
    |f info |

    Processor removeTimedBlock:checkBlock.
    f := changeFileName asFilename.
    (info := f info) isNil ifTrue:[
	self newLabel:'(unaccessable)'
    ] ifFalse:[
	(info modified) > changeFileTimestamp ifTrue:[
	    self newLabel:'(outdated)'.
	    autoUpdate ifTrue:[
		self doUpdate
	    ]
	] ifFalse:[
	    self newLabel:''
	]
    ].
    Processor addTimedBlock:checkBlock afterSeconds:5.

    "Created: 8.9.1995 / 19:30:19 / claus"
    "Modified: 8.9.1995 / 19:38:18 / claus"
    "Modified: 1.11.1996 / 20:22:56 / cg"
!

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

    "Modified: 6.12.1995 / 17:06:31 / cg"
!

clearCodeView
    self unselect "changeListView deselect".
    codeView contents:nil.
    changeNrShown := nil
!

compareChange:changeNr
    "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 sClass thisClassSym|

    aStream := self streamForChange:changeNr.
    aStream isNil ifTrue:[^ self].

    showDiff := false.

    (self changeIsFollowupMethodChange:changeNr) ifFalse:[
        sawExcla := aStream peekFor:(aStream class chunkSeparator).
        chunk := aStream nextChunk.
    ] ifTrue:[
        chunk := (changeChunks at:changeNr).
        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'
                        ]
                    ]
                ]
            ].
            selector == #subclass:instanceVariableNames:classVariableNames:poolDictionaries:category: ifTrue:[
                sClass := (parseTree receiver evaluate).
                sClass isBehavior ifTrue:[
                    (self checkClassIsLoaded:sClass) ifTrue:[
                        thisClassSym := (parseTree arguments at:1) evaluate.
                        thisClass := Smalltalk at:thisClassSym ifAbsent:nil.
                        thisClass notNil ifTrue:[
                            thisClass instanceVariableString = (parseTree arguments at:2) evaluate ifTrue:[
                                thisClass classVariableString = (parseTree arguments at:3) evaluate ifTrue:[
                                    ((thisClass sharedPools size == 0) and:[(parseTree arguments at:4) evaluate = '']) ifTrue:[
                                        thisClass category = (parseTree arguments at:5) evaluate ifTrue:[
                                            outcome := 'change has no effect\\(same definition)'.
                                        ]
                                    ]
                                ]
                            ]
                        ]
                    ]
                ]
            ]
        ]
    ] 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:(resources string:'current version (in image)')
                                and:newSource label:(resources string:'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:[
        outcome := (resources string:outcome) withCRs.
        beep ifTrue:[
            self warn:outcome.
        ] ifFalse:[
            self information:outcome.
        ]
"/        Transcript showCR:outcome.
    ].

    "Created: / 24.11.1995 / 14:30:46 / cg"
    "Modified: / 6.7.1998 / 17:07:47 / cg"
!

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 withExecuteCursorDo:[
	|numChanges classes selectors types excla sawExcla
	 changeNr chunk aParseTree parseTreeChunk
	 thisClass thisSelector codeChunk codeParser
	 compressThis|

	numChanges := self numberOfChanges.
	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:[
	    aStream position:(changePositions at:changeNr).
	    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 < self numberOfChanges] 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:[
	    changeListView setSelection:nil.
	    index := deleteSet size.
	    [index > 0] whileTrue:[
		self silentDeleteChange:(deleteSet at:index).
		index := index - 1
	    ].
	    self setChangeList.
	    "
	     scroll back a bit, if we are left way behind the list
	    "
	    changeListView firstLineShown > self numberOfChanges ifTrue:[
		changeListView makeLineVisible:self numberOfChanges
	    ].
	    self clearCodeView
	]
    ].
    self newLabel:''.

    "Created: / 29.10.1997 / 01:02:44 / cg"
    "Modified: / 29.10.1997 / 01:26:59 / cg"
!

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
!

deleteChange:changeNr
    "delete a change"

    self deleteChangesFrom:changeNr to:changeNr
!

deleteChangesFrom:start to:stop
    "delete a range of changes"

    changeListView setSelection:nil.
    stop to:start by:-1 do:[:changeNr |
        self silentDeleteInternalChange:changeNr.
        changeListView removeIndex:changeNr.
    ].

"/    changeListView contentsChanged.
"/    changeListView redrawFromLine:start.

"/    self setChangeList

    "Modified: / 18.5.1998 / 14:22:27 / cg"
!

fullClassNameOfChange:changeNr
    "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|

    changeNr isNil ifTrue:[^ nil].

    "
     first look, if not already known
    "
    name := changeClassNames at:changeNr.
    name notNil ifTrue:[^ name].

    prevMethodDefNr := changeNr.
    [changeIsFollowupMethodChange at:prevMethodDefNr] whileTrue:[
        prevMethodDefNr := prevMethodDefNr - 1.
    ].

    "
     get the chunk
    "
    chunk := changeChunks at:prevMethodDefNr.
    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.
                changeClassNames at:changeNr put: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.

        aParseTree == #Error ifTrue:[
            (chunk includesString:'comment') ifTrue:[
                "/ could be a comment ...
                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:' 
       #'ignoredMethodsFor:' 
       #'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'.
                ].
                changeClassNames at:changeNr put: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:changeNr.
        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
            ].
            changeClassNames at:changeNr put: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.
                changeClassNames at:changeNr put: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.
                changeClassNames at:changeNr put:name.
                ^ name
            ]
        ]
    ].
    ^ nil

    "Modified: / 3.8.1998 / 19:58:17 / cg"
!

makeChangeAPatch:changeNr
    "append change to patchfile"

    self appendChange:changeNr toFile:'patches'
!

makeChangePermanent:changeNr
    "rewrite the source file where change changeNr lies"

    self notify:'this is not yet implemented'
!

newLabel:how
    |l|

    (changeFileName ~= 'changes') ifTrue:[
        l := self class defaultLabel , ': ', changeFileName
    ] ifFalse:[
        l := self class defaultLabel
    ].
    l := l , ' ' , how.
    self label:l

    "Created: / 8.9.1995 / 19:32:04 / claus"
    "Modified: / 8.9.1995 / 19:39:29 / claus"
    "Modified: / 6.2.1998 / 13:27:01 / cg"
!

numberOfChanges
    ^ changePositions size

    "Created: 3.12.1995 / 18:15:39 / cg"
!

queryCloseText
    "made this a method for easy redefinition in subclasses"

    ^ 'Quit without updating changeFile ?'
!

readChangesFile
    "read the changes file, create a list of header-lines (changeChunks)
     and a list of chunk-positions (changePositions)"

    ^ self readChangesFileInBackground:false
!

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|

    editingClassSource := false.

    maxLen := 60.

    f := changeFileName asFilename.
    aStream :=  f readStream.
    aStream isNil ifTrue:[^ nil].

    self newLabel:'updating ...'.

    i := f info.
    changeFileSize := i size.
    changeFileTimestamp := i modified.

    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|

            changeChunks := OrderedCollection new.
            changeHeaderLines := OrderedCollection new.
            changePositions := OrderedCollection new.
            changeTimeStamps := OrderedCollection new.
            changeIsFollowupMethodChange := OrderedCollection new.

            excla := aStream class chunkSeparator.

            [aStream atEnd] whileFalse:[
                |entry changeDelta changeString changeType 
                 line s l changeClass sawExcla category 
                 chunkText chunkPos sel|

                "
                 get a chunk (separated by excla)
                "
                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
                            ].
                        ].

                        changeChunks add:chunkText.
                        changePositions add:chunkPos.
                        changeTimeStamps add:timeStampInfo.
                        changeIsFollowupMethodChange add:false.

                        headerLine := nil.
                        changeDelta := ' '.

                        sawExcla ifFalse:[
                            (chunkText startsWith:'''---- snap') ifTrue:[
                                changeType := ''.
                                headerLine := chunkText.
                                changeString := (chunkText contractTo:maxLen).
                                timeStampInfo := nil.
                            ] ifFalse:[

                                |p cls clsName|

                                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.
                                ] ifFalse:[
                                    sel := nil.    
                                ].
                                (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 := '-'.
                                            ] ifFalse:[
                                                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)'.
                                    clsName := (p args at:1) evaluate.
                                    cls := Smalltalk at:clsName ifAbsent:nil.
                                    cls isNil ifTrue:[
                                        changeDelta := '+'.
                                    ]
                                ].
                            ]
                        ] ifTrue:[ "sawExcla"
                            |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:[
                                        changeChunks add:chunkText.
                                        changePositions add:methodPos.
                                        changeTimeStamps add:timeStampInfo.
                                        changeIsFollowupMethodChange add: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 in: ''' , 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 := '='
                                                        ]
                                                    ]
                                                ]
                                            ]
                                        ]
                                    ].
                                    entry := MultiColListEntry new.
                                    entry tabulatorSpecification:tabSpec.
                                    entry colAt:1 put:changeDelta.
                                    entry colAt:2 put:changeString.
                                    entry colAt:3 put:changeType.
                                    timeStampInfo notNil ifTrue:[
                                        entry colAt:4 put:timeStampInfo.
                                    ].    
                                    changeHeaderLines add:entry
                                ].
                                changeString := nil.
                                headerLine := nil.

                            ]
                        ].
                        changeString notNil ifTrue:[
                            entry := MultiColListEntry new.
                            entry tabulatorSpecification:tabSpec.
                            entry colAt:1 put:changeDelta.
                            entry colAt:2 put:changeString.
                            entry colAt:3 put:changeType.
                            timeStampInfo notNil ifTrue:[
                                entry colAt:4 put:timeStampInfo.
                            ].    
                            changeHeaderLines add:entry
                        ] ifFalse:[
                            headerLine notNil ifTrue:[
                                changeHeaderLines add:headerLine
                            ]
                        ]
                    ]
                ]
            ].
            changeClassNames := OrderedCollection new grow:(changeChunks size).
            anyChanges := false
        ] valueNowOrOnUnwindDo:[
            aStream close.
            inBackground ifTrue:[myProcess priority:myPriority].
        ].
    ].

    self checkIfFileHasChanged

    "Modified: / 27.8.1995 / 23:06:55 / claus"
    "Modified: / 17.7.1998 / 11:10:07 / cg"
!

selectorOfMethodChange:changeNr
    "return a method-changes selector, or nil if its not a methodChange"

    |source parser sel chunk aParseTree |

    source := self sourceOfMethodChange:changeNr.
    source isNil ifTrue:[
        (self classNameOfChange:changeNr) notNil ifTrue:[
            chunk := changeChunks at:changeNr.
            chunk isNil ifTrue:[^ nil].       "mhmh - empty"
            aParseTree := Parser parseExpression:chunk.
            (aParseTree isNil or:[aParseTree == #Error]) ifTrue:[
                ^ nil        "seems strange ... (could be a comment)"
            ].
            aParseTree isMessage ifFalse:[
                ^ nil        "very strange ... (whats that ?)"
            ].
            sel := aParseTree selector.
            (#(
                #'removeSelector:' 
            ) includes:sel) ifTrue:[
                sel := aParseTree arguments at:1.
                sel isConstant ifTrue:[
                    sel := sel evaluate.
                    sel isSymbol ifTrue:[
                        ^ sel
                    ]
                ]
            ]
        ].
        ^ nil
    ].

    parser := Parser 
                parseMethod:source 
                in:nil 
                ignoreErrors:true 
                ignoreWarnings:true.

    (parser notNil and:[parser ~~ #Error]) ifTrue:[
        sel := parser selector.
    ].
    ^ sel

    "Created: 24.11.1995 / 14:30:46 / cg"
    "Modified: 5.9.1996 / 17:12:50 / cg"
!

setChangeList
    "extract type-information from changes and stuff into top selection
     view"

    changeListView setList:changeHeaderLines expandTabs:false redraw:false.
    changeListView invalidate.

    "/ changeListView deselect.

    "Modified: / 18.5.1998 / 14:29:10 / cg"
!

silentDeleteChange:changeNr
    "delete a change do not update changeListView"

    anyChanges := true.
    changeChunks removeIndex:changeNr.
    changePositions removeIndex:changeNr.
    changeClassNames removeIndex:changeNr.
    changeHeaderLines removeIndex:changeNr.
    changeTimeStamps removeIndex:changeNr

    "Modified: 18.11.1995 / 17:08:44 / cg"
!

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:index.
        thisClassName = aClassName ifTrue:[
            self silentDeleteChange:index.
            numDeleted := numDeleted + 1.
        ].
        index := index - 1
    ].
    ^ numDeleted

!

silentDeleteInternalChange:changeNr
    "delete a change do not update changeListView"

    anyChanges := true.
    changeChunks removeIndex:changeNr.
    changePositions size >= changeNr ifTrue:[changePositions removeIndex:changeNr].
    changeClassNames size >= changeNr ifTrue:[changeClassNames removeIndex:changeNr].
    changeTimeStamps size >= changeNr ifTrue:[changeTimeStamps removeIndex:changeNr]

    "Created: / 7.3.1997 / 16:28:32 / cg"
    "Modified: / 7.2.1998 / 19:59:11 / cg"
    "Modified: / 26.2.1998 / 18:20:48 / stefan"
!

sourceOfMethodChange:changeNr
    "return a method-changes source code, or nil if its not a methodChange."

    |aStream chunk sawExcla parseTree sourceChunk|

    aStream := self streamForChange:changeNr.
    aStream isNil ifTrue:[^ nil].

    (self changeIsFollowupMethodChange:changeNr) ifFalse:[
        sawExcla := aStream peekFor:(aStream class chunkSeparator).
        chunk := aStream nextChunk.
    ] ifTrue:[
        chunk := (changeChunks at:changeNr).
        sawExcla := true.
    ].

    sawExcla ifTrue:[
        parseTree := Parser parseExpression:chunk.
        (parseTree notNil and:[parseTree isMessage]) ifTrue:[
            (#(#methodsFor: 
               #privateMethodsFor:
               #publicMethodsFor:
               #ignoredMethodsFor:
               #protectedMethodsFor:) 
            includes:parseTree selector) ifTrue:[
                sourceChunk := aStream nextChunk.
            ]
        ].
    ].
    aStream close.
    ^ sourceChunk

    "Created: / 5.9.1996 / 17:11:32 / cg"
    "Modified: / 3.8.1998 / 20:00:21 / cg"
!

streamForChange:changeNr
    "answer a stream for change"
 
    |aStream|

    (changeNr between:1 and:changePositions size) ifFalse:[^ nil].
    aStream := FileStream readonlyFileNamed:changeFileName.
    aStream isNil ifTrue:[^ nil].
    aStream position:(changePositions at:changeNr).
    ^ aStream
!

unselect
    "common unselect"

    changeListView setSelection:nil.

    "Modified: 25.5.1996 / 13:02:49 / cg"
!

withSelectedChangeDo:aBlock
    "just a helper, check for a selected change and evaluate aBlock
     with busy cursor"

    |changeNr|

    changeNr := changeListView selection.
    changeNr notNil ifTrue:[
	self withExecuteCursorDo:[
	    aBlock value:changeNr
	]
    ]

    "Modified: 14.12.1995 / 20:58:45 / cg"
!

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

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

    inStream := FileStream readonlyFileNamed:changeFileName.
    inStream isNil ifTrue:[^ false].

    self withCursor:(Cursor write) do:[
        |excla sawExcla done first chunk
         nChanges "{Class:SmallInteger}" |

        Stream writeErrorSignal handle:[:ex |
            self warn:('could not update the changes file.\\' , ex errorString) withCRs.
            tempfile exists ifTrue:[tempfile remove].
            ^ false
        ] do:[

            excla := inStream class chunkSeparator.
            nChanges := self numberOfChanges.

            1 to:nChanges do:[:index |
                inStream position:(changePositions at:index).
                sawExcla := inStream peekFor:excla.
                chunk := inStream nextChunk.

                (chunk notNil
                and:[(chunk startsWith:'''---- snap') not]) ifTrue:[
                    (stamp := changeTimeStamps at:index) notNil ifTrue:[
                        outStream nextPutAll:'''---- timestamp ' , stamp , ' ----'''.
                        outStream nextPut:excla; cr.
                    ].
                ].

                sawExcla ifTrue:[
                    outStream nextPut:excla.
                    outStream nextChunkPut:chunk.
                    outStream cr; cr.
                    "
                     a method-definition chunk - output followups
                    "
                    done := false.
                    first := true.
                    [done] whileFalse:[
                        chunk := inStream nextChunk.
                        chunk isNil ifTrue:[
                            outStream cr; cr.
                            done := true
                        ] ifFalse:[
                            chunk isEmpty ifTrue:[
                                outStream space; nextChunkPut:chunk; cr; cr.
                                done := true.
                            ] ifFalse:[
                                first ifFalse:[
                                    outStream cr; cr.
                                ].
                                outStream nextChunkPut:chunk.
                            ].
                        ].
                        first := false.
                    ].
                ] ifFalse:[
                    outStream nextChunkPut:chunk.
                    outStream cr
                ]
            ].
            outStream close.
            inStream close.
        ].

        f := changeFileName asFilename.
        f renameTo:(f withSuffix:'bak').
        tempfile renameTo:changeFileName.
        anyChanges := false
    ].
    ^ true

    "Modified: / 2.12.1996 / 22:29:15 / stefan"
    "Modified: / 21.4.1998 / 17:50:11 / cg"
! !

!ChangesBrowser methodsFor:'termination'!

closeRequest
    "window manager wants us to go away"

    |action again|

    anyChanges ifTrue:[
        again := true.
        [again] whileTrue:[
            action := OptionBox 
                              request:(resources at:'changefile has not been updated from the modified changelist.\\Update before closing ?') withCRs
                              label:'ChangesBrowser'
                              form:(WarningBox iconBitmap)
                              buttonLabels:(resources array:#('cancel' 'don''t update' 'update'))
                              values:#(#abort #ignore #save)
                              default:#save.

            again := false.
            action == #abort ifTrue:[^ self].
            action  == #save ifTrue:[
                again := self writeBackChanges not
            ].
        ]
    ].
    super closeRequest

    "Modified: / 31.7.1997 / 18:29:06 / cg"
    "Created: / 3.8.1998 / 19:54:13 / cg"
!

saveAndTerminate
    "update the changes file and quit.
     Dont depend on this being sent, not all window managers
     send it; instead, they simply destroy the view."

    anyChanges ifTrue:[
        self writeBackChanges.
    ].
    super saveAndTerminate

    "Modified: / 3.8.1998 / 19:54:00 / cg"
! !

!ChangesBrowser methodsFor:'user interaction'!

autoUpdate:aBoolean
    autoUpdate := aBoolean

    "Created: 3.12.1995 / 14:14:24 / cg"
    "Modified: 3.12.1995 / 14:20:45 / cg"
!

changeSelection:lineNr
    "show a change in the codeView"

    |aStream sawExcla chunk|

    aStream := self streamForChange:lineNr.
    aStream isNil ifTrue:[
        codeView initializeDoITAction.
        ^ self
    ].
    sawExcla := aStream peekFor:(aStream class chunkSeparator).
    chunk := aStream nextChunk.
    sawExcla ifTrue:[
        chunk := aStream nextChunk
    ].
    aStream close.
    codeView contents:chunk.
    codeView acceptAction:[:theCode | self doApply "noChangesAllowed"].
    codeView doItAction:[:theCode |
        |clsName cls|

        clsName := self classNameOfChange:lineNr.
        cls := Smalltalk at:clsName ifAbsent:nil.
        Compiler 
            evaluate:theCode 
            in:nil 
            receiver:cls 
            notifying:self 
            logged:true 
            ifFail:nil 
    ].
    changeNrShown := lineNr.

    "Modified: / 28.2.1999 / 15:26:46 / cg"
!

doApply
    "user wants a change to be applied"

    self withSelectedChangeDo:[:changeNr |
	skipSignal := nil.
	self applyChange:changeNr.
	self autoSelect:(changeNr + 1)
    ]
!

doApplyAll
    "user wants all changes to be applied"

    self withExecuteCursorDo:[
	|lastNr "{ Class: SmallInteger }" |

	self clearCodeView.
	skipSignal isNil ifTrue:[skipSignal := Signal new].
	lastNr := self numberOfChanges.
	1 to:lastNr do:[:changeNr |
	    changeListView setSelection:changeNr.
	    self applyChange:changeNr
	].
	self autoSelectLast
    ]

    "Modified: 21.1.1997 / 22:26:30 / cg"
!

doApplyClassRest
    "user wants all changes for this class from changeNr to be applied"

    self withSelectedChangeDo:[:changeNr |
	|thisClassName classNameToApply lastChange
	 lastNr "{ Class: SmallInteger }" |

	classNameToApply := self classNameOfChange:changeNr.
	classNameToApply notNil ifTrue:[
	    self clearCodeView.
	    skipSignal isNil ifTrue:[skipSignal := Signal new].

	    lastNr := self numberOfChanges.
	    changeNr to:lastNr do:[:changeNr |
		thisClassName := self classNameOfChange:changeNr.
		thisClassName = classNameToApply ifTrue:[
		    changeListView setSelection:changeNr.
		    self applyChange:changeNr.
		    lastChange := changeNr
		].
	    ].
	    self autoSelect:lastChange.
	]
    ]

    "Modified: 21.1.1997 / 22:26:04 / cg"
!

doApplyRest
    "user wants all changes from changeNr to be applied"

    self withSelectedChangeDo:[:changeNr |
	|lastNr "{ Class: SmallInteger }" |

	self clearCodeView.
	skipSignal isNil ifTrue:[skipSignal := Signal new].

	lastNr := self numberOfChanges.
	changeNr to:lastNr do:[:changeNr |
	    changeListView setSelection:changeNr.
	    self applyChange:changeNr
	].
	self autoSelect:self numberOfChanges.
    ]

    "Modified: 21.1.1997 / 22:25:29 / cg"
!

doBrowse
    "user wants a browser on the class of a change"

    |changeNr className cls isMeta|

    changeNr := changeListView selection.
    changeNr notNil ifTrue:[
        className := self fullClassNameOfChange:changeNr.
        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:changeNr)
            ] ifFalse:[
                self warn:'class not found: ''' , className , ''''.
            ]
        ] ifFalse:[
            self warn:'could not extract classname from change'.
        ]
    ]

    "Created: / 24.11.1995 / 23:13:24 / cg"
    "Modified: / 31.7.1998 / 20:17:42 / cg"
!

doCompare
    "compare change with current system version
     - give a note in transcript"

    |changeNr|

    changeNr := changeListView selection.
    changeNr notNil ifTrue:[
	self withExecuteCursorDo:[
	    self compareChange:changeNr
	]
    ].
    self newLabel:''

    "Modified: 24.2.1996 / 19:37:19 / cg"
!

doCompress
    "compress the change-set; this replaces multiple method-changes by the last 
     (i.e. the most recent) change"

    self compressForClass:nil

    "Modified: / 29.10.1997 / 01:03:26 / cg"
!

doCompressClass
    "compress changes for the selected class.
     this replaces multiple method-changes by the last (i.e. the most recent) change."

    self withSelectedChangeDo:[:changeNr |
	| classNameToCompress |

	classNameToCompress := self classNameOfChange:changeNr.
	classNameToCompress notNil ifTrue:[
	    self compressForClass:classNameToCompress.
	]
    ]

    "Created: / 29.10.1997 / 01:05:16 / cg"
    "Modified: / 29.10.1997 / 01:06:22 / cg"
!

doDelete
    "delete currently selected change"

    |changeNr|

    changeNr := changeListView selection.
    changeNr notNil ifTrue:[
	self deleteChange:changeNr.
	self autoSelectOrEnd:changeNr
    ]
!

doDeleteClassAll
    "delete all changes with same class as currently selected change"

    self withSelectedChangeDo:[:changeNr |
        |classNameToDelete numDeletedBefore|

        classNameToDelete := self classNameOfChange:changeNr.
        classNameToDelete notNil ifTrue:[
            changeListView setSelection:nil.
            self silentDeleteChangesFor:classNameToDelete
                                   from:changeNr
                                     to:(self numberOfChanges).
            numDeletedBefore := self 
                                   silentDeleteChangesFor:classNameToDelete
                                   from:1
                                   to:(changeNr-1).
            self setChangeList. 
            self autoSelectOrEnd:(changeNr - numDeletedBefore)
        ]
    ]

    "Created: / 13.12.1995 / 16:07:14 / cg"
    "Modified: / 28.1.1998 / 20:42:14 / cg"
!

doDeleteClassFromBeginning
    "delete changes with same class as currently selected change from the beginning
     up to the selected change.
     Useful to get rid of obsolete changes before a fileout or checkin entry."

    self withSelectedChangeDo:[:changeNr |
        |classNameToDelete prevSelection numDeleted|

        classNameToDelete := self classNameOfChange:changeNr.
        classNameToDelete notNil ifTrue:[
            prevSelection := changeNr.
            changeListView setSelection:nil.
            numDeleted := self 
                                silentDeleteChangesFor:classNameToDelete 
                                from:1 
                                to:changeNr.
            self setChangeList.
            self autoSelectOrEnd:(changeNr + 1 - numDeleted)
        ]
    ].

    "Created: 13.12.1995 / 15:41:58 / cg"
    "Modified: 25.5.1996 / 12:26:34 / cg"
!

doDeleteClassRest
    "delete rest of changes with same class as currently selected change"

    self withSelectedChangeDo:[:changeNr |
        | classNameToDelete |

        classNameToDelete := self classNameOfChange:changeNr.
        classNameToDelete notNil ifTrue:[
            changeListView setSelection:nil.
            self silentDeleteChangesFor:classNameToDelete 
                                   from:changeNr
                                     to:(self numberOfChanges).
            self setChangeList.
            self autoSelectOrEnd:changeNr
        ]
    ]

    "Modified: / 18.5.1998 / 14:25:07 / cg"
!

doDeleteRest
    "delete all changes from current to the end"

    |changeNr|

    changeNr := changeListView selection.
    changeNr notNil ifTrue:[
	self deleteChangesFrom:changeNr to:(self numberOfChanges).
	self clearCodeView.
	self autoSelectOrEnd:changeNr-1
    ]
!

doFileoutAndDeleteClassAll
    "first fileOut the selected changes class then delete all changes
     for it."

    self withSelectedChangeDo:[:changeNr |
	| className class |

	className := self classNameOfChange:changeNr.
	className notNil ifTrue:[
	    class := Smalltalk classNamed:className.
	    class notNil ifTrue:[
		Class fileOutErrorSignal handle:[:ex |
		    self warn:('fileout failed: ' , ex errorString).
		] do:[
		    class fileOut.
		    self doDeleteClassAll
		].
	    ].

	].
    ]

    "Modified: 6.9.1995 / 17:11:16 / claus"
!

doMakePatch
    "user wants a change to be made a patch
     - copy it over to the patches file"

    |changeNr|

    changeNr := changeListView selection.
    changeNr notNil ifTrue:[
	self makeChangeAPatch:changeNr.
	self autoSelect:(changeNr + 1)
    ]
!

doMakePermanent
    "user wants a change to be made permanent
     - rewrite the source file where this change has to go"

    |yesNoBox|

    yesNoBox := YesNoBox new.
    yesNoBox title:(resources at:'Warning: this operation cannot be undone').
    yesNoBox okText:(resources at:'continue') noText:(resources at:'abort').
    yesNoBox okAction:[   |changeNr|

			  changeNr := changeListView selection.
			  changeNr notNil ifTrue:[
			      self makeChangePermanent:changeNr.
			      self autoSelect:(changeNr + 1)
			  ]
		      ].
    yesNoBox showAtPointer.
    yesNoBox destroy

    "Modified: 7.1.1997 / 23:03:33 / cg"
!

doSave
    "user wants a change to be appended to a file"

    |changeNr fileName|

    changeNr := changeListView selection.
    changeNr notNil ifTrue:[
        fileName := Dialog
                        requestFileName:'append change to:'
                        default:''
                        ok:'append'
                        abort:'abort'
                        pattern:'*.chg'.

        fileName notNil ifTrue:[
            self withCursor:(Cursor write) do:[
                self appendChange:changeNr toFile:fileName.
            ].
            self autoSelect:(changeNr + 1)
        ].
    ]
!

doSaveClass
    "user wants changes for some class from current to end to be appended to a file"

    self doSaveClassFrom:1
!

doSaveClassAll
    "user wants changes for some class from current to end to be appended to a file"

    self doSaveClassFrom:1
!

doSaveClassFrom:startNr
    "user wants changes from current to end to be appended to a file"

    |changeNr classNameToSave|

    changeNr := changeListView selection.
    changeNr notNil ifTrue:[
	classNameToSave := self classNameOfChange:changeNr.
	classNameToSave notNil ifTrue:[
	    self saveClass:classNameToSave from:startNr
	]
    ]
!

doSaveClassRest
    "user wants changes for some class from current to end to be appended to a file"

    |changeNr|

    changeNr := changeListView selection.
    changeNr notNil ifTrue:[
	self doSaveClassFrom:changeNr
    ]
!

doSaveRest
    "user wants changes from current to end to be appended to a file"

    |changeNr fileName|

    changeNr := changeListView selection.
    changeNr notNil ifTrue:[
	fileName := Dialog
			requestFileName:'append changes to:'
			default:''
			ok:'append'
			abort:'abort'
			pattern:'*.chg'.

	fileName notNil ifTrue:[
	    self withCursor:(Cursor write) do:[
		changeNr to:(self numberOfChanges) do:[:changeNr |
		    changeListView setSelection:changeNr.
		    (self appendChange:changeNr toFile:fileName) ifFalse:[
			^ self
		    ]
		]
	    ]
	].
    ]

    "Modified: 25.5.1996 / 12:26:41 / cg"
!

doUpdate
    "reread the changes-file"

    self readChangesFileInBackground:true.
    realized ifTrue:[
	self setChangeList.
    ]

!

doWriteBack
    "write back the list onto the changes file"

    anyChanges ifTrue:[
	(self writeBackChanges) ifTrue:[
	    realized ifTrue:[
		self readChangesFileInBackground:false.
		realized ifTrue:[
		    self setChangeList
		]
	    ]
	]
    ]

    "Modified: 5.9.1996 / 17:19:46 / cg"
!

doubleClickOnChange:lineNr
    self doBrowse

    "Created: / 6.2.1998 / 13:08:49 / cg"
!

findClass
    |nm current savedCursor search|

    lastSearchType := #class.

    changeNrShown notNil ifTrue:[
        current := self classNameOfChange:changeNrShown.
    ].
    nm := Dialog 
            request:'class to search for:' 
            initialAnswer:current
            onCancel:nil.
    nm isNil ifTrue:[
        ^ self
    ].

    changeNrShown isNil ifTrue:[
        search := 1.
    ] ifFalse:[
        search := changeNrShown + 1
    ].

    self withCursor:Cursor questionMark do:[
        |lastNr nr thisClass |

        lastNr := self numberOfChanges.
        nr := search.
        [nr <= lastNr] whileTrue:[
            thisClass := self classNameOfChange:nr.
            (thisClass = nm
            or:[nm includesMatchCharacters and:[nm match:thisClass]]) ifTrue:[
                changeListView setSelection:nr.
                self changeSelection:nr.
                ^ self
            ].
            nr := nr + 1.
        ].
    ].
    self beep.

    savedCursor := cursor.
    self cursor:(Cursor cross).
    Delay waitForMilliseconds:300.
    self cursor:savedCursor

    "Created: / 8.4.1997 / 11:00:16 / cg"
    "Modified: / 18.4.1997 / 12:47:52 / stefan"
    "Modified: / 18.6.1998 / 22:27:33 / cg"
!

findNext
    lastSearchType == #selector ifTrue:[
        ^ self findNextForSelector
    ].
    ^ self findNextForClass

    "Created: / 18.6.1998 / 22:15:00 / cg"
    "Modified: / 18.6.1998 / 22:15:25 / cg"
!

findNextForClass
    |savedCursor|

    lastSearchType := #class.
    changeNrShown isNil ifTrue:[^ self].

    self withCursor:Cursor questionMark do:[
        |lastNr cls nr thisClass |

        cls := self classNameOfChange:changeNrShown.
        cls isNil ifTrue:[^ self].

        lastNr := self numberOfChanges.
        nr := changeNrShown + 1.
        [nr <= lastNr] whileTrue:[
            thisClass := self classNameOfChange:nr.
            (thisClass = cls
            or:[cls includesMatchCharacters and:[cls match:thisClass]]) ifTrue:[
                changeListView setSelection:nr .
                self changeSelection:nr.
                ^ self
            ].
            nr := nr + 1.
        ].
    ].
    self beep.

    savedCursor := cursor.
    self cursor:(Cursor cross).
    Delay waitForMilliseconds:300.
    self cursor:savedCursor

    "Created: / 21.1.1997 / 22:28:07 / cg"
    "Modified: / 18.4.1997 / 12:48:18 / stefan"
    "Modified: / 18.6.1998 / 22:29:40 / cg"
!

findNextForSelector
    |savedCursor|

    lastSearchType := #selector.
    changeNrShown isNil ifTrue:[^ self].

    self withCursor:Cursor questionMark do:[
        |lastNr sel nr thisSelector |

        sel := self selectorOfMethodChange:changeNrShown.
        sel isNil ifTrue:[^ self].

        lastNr := self numberOfChanges.
        nr := changeNrShown + 1.
        [nr <= lastNr] whileTrue:[
            thisSelector := self selectorOfMethodChange:nr.
            (thisSelector = sel
            or:[sel includesMatchCharacters and:[sel match:thisSelector]]) ifTrue:[
                changeListView setSelection:nr .
                self changeSelection:nr.
                ^ self
            ].
            nr := nr + 1.
        ].
    ].
    self beep.

    savedCursor := cursor.
    self cursor:(Cursor cross).
    Delay waitForMilliseconds:300.
    self cursor:savedCursor

    "Modified: / 18.4.1997 / 12:48:18 / stefan"
    "Created: / 18.6.1998 / 22:17:37 / cg"
    "Modified: / 18.6.1998 / 22:29:33 / cg"
!

findNextSnapshot
    |savedCursor|

    changeNrShown isNil ifTrue:[^ self].

    self withCursor:Cursor questionMark do:[
        |cls nr lastNr words chunk|

        Object userInterruptSignal handle:[:ex |
            self beep.
            ^ self
        ] do:[
            lastNr := self numberOfChanges.
            nr := changeNrShown + 1.
            [nr <= lastNr] whileTrue:[
                "
                 get the chunk
                "
                chunk := changeChunks at:nr.
                chunk isNil ifTrue:[^ nil].       "mhmh - empty"

                (chunk startsWith:'''---') ifTrue:[
                    words := chunk asCollectionOfWords.
                    words size > 2 ifTrue:[
                        (words at:2) = 'snapshot' ifTrue:[
                            changeListView setSelection:nr .
                            self changeSelection:nr.
                            ^ self
                        ].
                    ].
                ].
                nr := nr + 1.
            ].
        ]
    ].
    self beep.

    savedCursor := cursor.
    self cursor:(Cursor cross).
    Delay waitForMilliseconds:300.
    self cursor:savedCursor

    "Modified: / 18.4.1997 / 12:48:41 / stefan"
    "Created: / 22.8.1998 / 15:58:49 / cg"
    "Modified: / 22.8.1998 / 15:59:38 / cg"
!

findPrevious
    lastSearchType == #selector ifTrue:[
        ^ self findPreviousForSelector
    ].
    ^ self findPreviousForClass

    "Created: / 18.6.1998 / 22:15:15 / cg"
!

findPreviousForClass
    |savedCursor|

    lastSearchType := #class.
    changeNrShown isNil ifTrue:[^ self].

    self withCursor:Cursor questionMark do:[
        |cls nr thisClass |

        Object userInterruptSignal handle:[:ex |
            self beep.
            ^ self
        ] do:[
            cls := self classNameOfChange:changeNrShown.
            cls isNil ifTrue:[^ self].

            nr := changeNrShown - 1.
            [nr >= 1] whileTrue:[
                thisClass := self classNameOfChange:nr.
                (thisClass = cls
                or:[cls includesMatchCharacters and:[cls match:thisClass]]) ifTrue:[
                    changeListView setSelection:nr .
                    self changeSelection:nr.
                    ^ self
                ].
                nr := nr - 1.
            ].
        ]
    ].
    self beep.

    savedCursor := cursor.
    self cursor:(Cursor cross).
    Delay waitForMilliseconds:300.
    self cursor:savedCursor

    "Modified: / 18.4.1997 / 12:48:41 / stefan"
    "Modified: / 18.6.1998 / 22:29:44 / cg"
!

findPreviousForSelector
    |savedCursor|

    lastSearchType := #selector.
    changeNrShown isNil ifTrue:[^ self].

    self withCursor:Cursor questionMark do:[
        |sel nr thisSelector |

        Object userInterruptSignal handle:[:ex |
            self beep.
            ^ self
        ] do:[
            sel := self selectorOfMethodChange:changeNrShown.
            sel isNil ifTrue:[^ self].

            nr := changeNrShown - 1.
            [nr >= 1] whileTrue:[
                thisSelector := self selectorOfMethodChange:nr.
                (thisSelector = sel
                or:[sel includesMatchCharacters and:[sel match:thisSelector]]) ifTrue:[
                    changeListView setSelection:nr .
                    self changeSelection:nr.
                    ^ self
                ].
                nr := nr - 1.
            ].
        ]
    ].
    self beep.

    savedCursor := cursor.
    self cursor:(Cursor cross).
    Delay waitForMilliseconds:300.
    self cursor:savedCursor

    "Modified: / 18.4.1997 / 12:48:41 / stefan"
    "Created: / 18.6.1998 / 22:18:19 / cg"
    "Modified: / 18.6.1998 / 22:29:29 / cg"
!

findPreviousSnapshot
    |savedCursor|

    changeNrShown isNil ifTrue:[^ self].

    self withCursor:Cursor questionMark do:[
        |cls nr words chunk|

        Object userInterruptSignal handle:[:ex |
            self beep.
            ^ self
        ] do:[
            nr := changeNrShown - 1.
            [nr >= 1] whileTrue:[
                "
                 get the chunk
                "
                chunk := changeChunks at:nr.
                chunk isNil ifTrue:[^ nil].       "mhmh - empty"

                (chunk startsWith:'''---') ifTrue:[
                    words := chunk asCollectionOfWords.
                    words size > 2 ifTrue:[
                        (words at:2) = 'snapshot' ifTrue:[
                            changeListView setSelection:nr .
                            self changeSelection:nr.
                            ^ self
                        ].
                    ].
                ].
                nr := nr - 1.
            ].
        ]
    ].
    self beep.

    savedCursor := cursor.
    self cursor:(Cursor cross).
    Delay waitForMilliseconds:300.
    self cursor:savedCursor

    "Modified: / 18.4.1997 / 12:48:41 / stefan"
    "Modified: / 18.6.1998 / 22:29:44 / cg"
    "Created: / 22.8.1998 / 15:57:34 / cg"
!

findSelector
    |sel current savedCursor search|

    lastSearchType := #selector.

    changeNrShown notNil ifTrue:[
        current := self selectorOfMethodChange:changeNrShown.
    ].
    sel := Dialog 
            request:'selector to search for:' 
            initialAnswer:current
            onCancel:nil.
    sel isNil ifTrue:[
        ^ self
    ].

    changeNrShown isNil ifTrue:[
        search := 1.
    ] ifFalse:[
        search := changeNrShown + 1
    ].

    self withCursor:Cursor questionMark do:[
        |lastNr cls nr thisSelector |

        lastNr := self numberOfChanges.
        nr := search.
        [nr <= lastNr] whileTrue:[
            thisSelector := self selectorOfMethodChange:nr.
            (thisSelector = sel
            or:[sel includesMatchCharacters and:[sel match:thisSelector]]) ifTrue:[
                changeListView setSelection:nr.
                self changeSelection:nr.
                ^ self
            ].
            nr := nr + 1.
        ].
    ].
    self beep.

    savedCursor := cursor.
    self cursor:(Cursor cross).
    Delay waitForMilliseconds:300.
    self cursor:savedCursor

    "Modified: / 18.4.1997 / 12:47:52 / stefan"
    "Created: / 18.6.1998 / 22:17:04 / cg"
    "Modified: / 18.6.1998 / 22:27:44 / cg"
!

noChangesAllowed
    "show a warning that changes cannot be changed"

    self warn:'changes are not allowed to be changed'
!

saveClass:aClassName from:startNr
    "user wants changes from current to end to be appended to a file"

    |changeNr fileName|

    changeNr := changeListView selection.
    changeNr notNil ifTrue:[
	fileName := Dialog
			requestFileName:'append changes for class to:'
			default:''
			ok:'append'
			abort:'abort'
			pattern:'*.chg'.

	fileName notNil ifTrue:[
	    self withCursor:(Cursor write) do:[
		startNr to:(self numberOfChanges) do:[:changeNr |
		    |thisClassName|

		    thisClassName := self classNameOfChange:changeNr.
		    thisClassName = aClassName ifTrue:[
			changeListView setSelection:changeNr.
			(self appendChange:changeNr toFile:fileName) ifFalse:[
			    ^ self
			]
		    ]
		]
	    ]
	].
    ]

    "Modified: 25.5.1996 / 12:26:44 / cg"
! !

!ChangesBrowser class methodsFor:'documentation'!

version
    ^ '$Header: /cvs/stx/stx/libtool/ChangesBrowser.st,v 1.152 1999-04-23 12:27:38 cg Exp $'
! !