Tools_ClassCategoryList.st
author Claus Gittinger <cg@exept.de>
Thu, 26 Feb 2004 20:03:55 +0100
changeset 5592 d9730a8d7c52
parent 5591 273637686948
child 5909 95cd2d9822b3
permissions -rw-r--r--
*** empty log message ***

"{ Package: 'stx:libtool' }"

"{ NameSpace: Tools }"

BrowserList subclass:#ClassCategoryList
	instanceVariableNames:'categoryList classes allSelected'
	classVariableNames:'AdditionalEmptyCategories'
	poolDictionaries:''
	category:'Interface-Browsers-New'
!

!ClassCategoryList class methodsFor:'documentation'!

documentation
"
    embeddable application displaying the class-categories.
    Provides an outputGenerator, which enumerates the classes in
    the selected categories.

    [author:]
	Claus Gittinger (cg@exept.de)
"
! !

!ClassCategoryList class methodsFor:'interface specs'!

singleCategoryWindowSpec
    "This resource specification was automatically generated
     by the UIPainter of ST/X."

    "Do not manually edit this!! If it is corrupted,
     the UIPainter may not be able to read the specification."

    "
     UIPainter new openOnClass:ClassCategoryList andSelector:#singleCategoryWindowSpec
     ClassCategoryList new openInterface:#singleCategoryWindowSpec
    "

    <resource: #canvas>

    ^ 
     #(#FullSpec
	#name: #singleCategoryWindowSpec
	#window: 
       #(#WindowSpec
	  #label: 'ClassCategoryList'
	  #name: 'ClassCategoryList'
	  #min: #(#Point 0 0)
	  #max: #(#Point 1024 721)
	  #bounds: #(#Rectangle 218 175 518 475)
	)
	#component: 
       #(#SpecCollection
	  #collection: #(
	   #(#LabelSpec
	      #label: 'ClassCategoryName'
	      #name: 'ClassCategoryLabel'
	      #layout: #(#LayoutFrame 0 0.0 0 0 0 1.0 25 0)
	      #translateLabel: true
	      #labelChannel: #classCategoryLabelHolder
	      #menu: #menuHolder
	    )
	   )
         
	)
      )
!

windowSpec
    "This resource specification was automatically generated
     by the UIPainter of ST/X."

    "Do not manually edit this!! If it is corrupted,
     the UIPainter may not be able to read the specification."

    "
     UIPainter new openOnClass:ClassCategoryList andSelector:#windowSpec
     ClassCategoryList new openInterface:#windowSpec
     ClassCategoryList open
    "

    <resource: #canvas>

    ^ 
     #(#FullSpec
	#name: #windowSpec
	#window: 
       #(#WindowSpec
	  #label: 'ClassCategoryList'
	  #name: 'ClassCategoryList'
	  #min: #(#Point 0 0)
	  #max: #(#Point 1024 721)
	  #bounds: #(#Rectangle 13 23 313 323)
	)
	#component: 
       #(#SpecCollection
	  #collection: #(
	   #(#SequenceViewSpec
	      #name: 'List'
	      #layout: #(#LayoutFrame 0 0.0 0 0.0 0 1.0 0 1.0)
	      #tabable: true
	      #model: #selectedCategories
	      #menu: #menuHolder
	      #hasHorizontalScrollBar: true
	      #hasVerticalScrollBar: true
	      #miniScrollerHorizontal: true
	      #isMultiSelect: true
	      #valueChangeSelector: #selectionChangedByClick
	      #useIndex: false
	      #sequenceList: #categoryList
	      #doubleClickChannel: #doubleClickChannel
	      #properties: 
	     #(#PropertyListDictionary
		#dragArgument: nil
		#dropArgument: nil
		#canDropSelector: #canDrop:
		#dropSelector: #doDrop:
	      )
	    )
	   )
         
	)
      )

    "Created: / 5.2.2000 / 13:42:11 / cg"
    "Modified: / 18.8.2000 / 20:11:49 / cg"
! !

!ClassCategoryList class methodsFor:'plugIn spec'!

aspectSelectors
    "This resource specification was automatically generated
     by the UIPainter of ST/X."

    "Do not manually edit this. If it is corrupted,
     the UIPainter may not be able to read the specification."

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

    ^ #(
	#(#doubleClickChannel #action )
	#forceGeneratorTrigger
	#hideUnloadedClasses
	#immediateUpdate
	#inGeneratorHolder
	#menuHolder
	#nameSpaceFilter
	#organizerMode
	#outGeneratorHolder
	#packageFilter
	#selectedCategories
	#selectionChangeCondition
	#slaveMode
	#updateTrigger
      ).

! !

!ClassCategoryList methodsFor:'aspects'!

categoryList
    categoryList isNil ifTrue:[
	categoryList := ValueHolder new.
    ].
    ^ categoryList

    "Created: / 25.2.2000 / 02:23:08 / cg"
!

categoryList:aValueHolder
    categoryList notNil ifTrue:[
	categoryList removeDependent:self
    ].
    categoryList := aValueHolder.
    categoryList notNil ifTrue:[
	categoryList addDependent:self
    ].

    "Created: / 18.8.2000 / 15:21:42 / cg"
!

classCategoryLabelHolder
    ^ self pseudoListLabelHolder
!

selectedCategories
    ^ self selectionHolder
!

selectedCategories:aValueHolder
    ^ self selectionHolder:aValueHolder
! !

!ClassCategoryList methodsFor:'change & update'!

delayedUpdate:something with:aParameter from:changedObject
    |selectedCategoriesHolder selectedCategories allSelectedBefore 
     nameListEntryForALL categoryOfClass wg|

    selectedCategories := self selectedCategoriesStrings.

    changedObject == Smalltalk ifTrue:[
	((something == #classVariables) 
	or:[something == #classDefinition]) ifTrue:[
	    listValid == true ifTrue:[
		categoryOfClass := aParameter category.
		(categoryList value includes:categoryOfClass) ifFalse:[
		    self invalidateList.                
		].
		slaveMode value ~~ true ifTrue:[
		    (selectedCategories includes:categoryOfClass) ifTrue:[
			"/ a selected class has changed
			"/ in order to give others a chance to update their list before,
			"/ this one is always enqueued for delayed update (even if immediateUpdate is true)
			"/ self enqueueDelayedUpdateOutputGenerator
			self enqueueMessage:#updateOutputGenerator for:self arguments:#()
		    ].
		].
	    ].
	    ^ self
	].
	something == #newClass ifTrue:[
	    categoryOfClass := aParameter category.
"/            listValid == true ifTrue:[
		(categoryList value includes:categoryOfClass) ifFalse:[
		    self invalidateList.                
		].
"/            ].
	    slaveMode value ~~ true ifTrue:[
		(selectedCategories includes:categoryOfClass) ifTrue:[
		    self enqueueDelayedUpdateOutputGenerator
		].
	    ].
	    ^ self
	].

	self invalidateList.

	(something == #classRemove 
	or:[something == #projectOrganization 
	or:[something == #organization]]) ifTrue:[
	    slaveMode value ~~ true ifTrue:[
		"/ sorry: cannot filter on category (already changed to #removed)
		self enqueueDelayedUpdateOutputGenerator
	    ].
	].
	^ self
    ].

    changedObject == ChangeSet ifTrue:[
	"/ remove all other change notifications from the eventQueue
	wg := self windowGroup.
	wg isNil ifTrue:[
	    "/ oops - should no longer be dependent...
	    changedObject removeDependent:self.
	] ifFalse:[
	    wg sensor 
		flushEventsFor:self 
		where:[:ev | ev isMessageSendEvent 
			     and:[ev selector == #delayedUpdate:with:from:
			     and:[(ev arguments at:3) == ChangeSet]]].
	].

	something == #addChange: ifTrue:[
	    "/ only need to invalidate, if that change changes my emphasis 
	    "/ (i.e. if its a new methodChange)
"/            self invalidateList.

	    aParameter isMethodChange ifTrue:[
		(ChangeSet current 
		    count:[:chg | chg notNil and:[chg isMethodChange
				  and:[ chg className = aParameter className ]]])
		== 1 ifTrue:[
		    "/ that methodChange is the first for this method.
		    aParameter changeClass ifNotNil:[
			self colorizeCategoryAsChanged:(aParameter changeClass category).
		    ]
		]
	    ].
	    ^ self
	].

	self invalidateList.
	^ self
    ].

    changedObject == nameSpaceFilter ifTrue:[
	"/ all might be more or less than before ...
	allSelected := false.
    ].
    changedObject == packageFilter ifTrue:[
	"/ all might be more or less than before ...
	allSelected := false.    
    ].

    selectedCategoriesHolder := self selectedCategories.
    changedObject == selectedCategoriesHolder ifTrue:[
	categoryList isNil ifTrue:[
	    "/ oops - hurry up
	    self invalidateList.
	].

	nameListEntryForALL := self class nameListEntryForALL.

	selectedCategories size > 1 ifTrue:[
	    (selectedCategories includes:nameListEntryForALL) ifTrue:[
		self makeSelectionOtherThanAllVisible.
	    ]
	].

	"/ if all selected before AND allSelected after, no need to update the output generator
	allSelectedBefore := allSelected ? false.
	allSelected := selectedCategories includes:nameListEntryForALL.
	(allSelectedBefore and:[allSelected]) ifTrue:[
	    ^ self
	].
    ].

    super delayedUpdate:something with:aParameter from:changedObject

    "Created: / 5.2.2000 / 13:42:12 / cg"
    "Modified: / 12.11.2001 / 19:36:16 / cg"
!

forceUpdateList
    self categoryList setValue:#().
    self updateList.
    self categoryList changed.
!

selectionChangedByClick
    "we are not interested in that - get another notification
     via the changed valueHolder"

    "Created: / 11.2.2000 / 11:39:48 / cg"
!

update:something with:aParameter from:changedObject
    |categoryOfClass|

    changedObject == Smalltalk ifTrue:[
	something == #methodDictionary ifTrue:[
	    ^ self 
	].
	something == #classComment ifTrue:[
	    ^ self.
	].
	(something == #classVariables
	or:[something == #classDefinition]) ifTrue:[
	    categoryOfClass := aParameter category.
	    ((self selectedCategories value ? #()) includes:categoryOfClass) ifTrue:[
"/ self halt.
		self updateOutputGenerator.                
	    ].
	].
	something == #methodTrap ifTrue:[
	    ^ self
	].
	something == #methodInClass ifTrue:[
	    ^ self
	].
	something == #methodInClassRemoved ifTrue:[
	    ^ self
	].
    ].

"/    changedObject == ChangeSet ifTrue:[
"/        something == #addChange: ifTrue:[
"/            ^ self
"/        ]
"/    ].

    super update:something with:aParameter from:changedObject

    "Modified: / 5.11.2001 / 14:31:18 / cg"
! !

!ClassCategoryList methodsFor:'drag & drop'!

canDrop:aDropContext
    |cat classes|

    classes := aDropContext dropObjects collect:[:obj | obj theObject].
    (classes contains:[:aClass | aClass isClass not]) ifTrue:[^ false].
    (classes contains:[:aClass | aClass isPrivate not]) ifFalse:[^ false].

    cat := self categoryAtTargetPointOf:aDropContext.
    cat isNil ifTrue:[
	^ false
    ].
    cat = '* obsolete *' ifTrue:[
	^  false
    ].

    (classes contains:[:aClass | aClass category ~= cat]) ifFalse:[^ false].
    ^ true.
!

categoryAtTargetPointOf:aDropContext
    |p categoryListView lineNr cat|

    p := aDropContext targetPoint.

    categoryListView := aDropContext targetWidget.

    lineNr := categoryListView lineAtY:p y.
    lineNr isNil ifTrue:[^ nil].

    cat := categoryList value at:lineNr.
    cat := cat string.
    cat = self class nameListEntryForALL ifTrue:[^ nil].

    ^ cat
!

doDrop:aDropContext
    |cat classes|

    classes := aDropContext dropObjects collect:[:aDropObject | aDropObject theObject].
    (classes contains:[:something | something isClass not]) ifTrue:[^ self].

    cat := self categoryAtTargetPointOf:aDropContext.
    cat notNil ifTrue:[
	self masterApplication moveClasses:classes toCategory:cat.
    ].
! !

!ClassCategoryList methodsFor:'generators'!

makeGenerator
    "return a generator which enumerates the classes from the selected category."

    |cats hideUnloadedClasses allName nameSpaceFilter packageFilter|

    cats := self selectedCategories value.
    cats size == 0 ifTrue:[
	^ #()
    ].
    cats := cats collect:[:each | each string].

    allName := self class nameListEntryForALL.

    hideUnloadedClasses := self hideUnloadedClasses value ? false.
    nameSpaceFilter := self nameSpaceFilter value.
    nameSpaceFilter notNil ifTrue:[
	(nameSpaceFilter includes:allName) ifTrue:[nameSpaceFilter := nil].
    ].
    packageFilter := self packageFilter value.
    packageFilter notNil ifTrue:[
	(packageFilter includes:allName) ifTrue:[packageFilter := nil].
    ].

    (cats includes:allName) ifTrue:[
	hideUnloadedClasses ifTrue:[
	    ^ Iterator on:[:whatToDo |
			       Smalltalk allClassesDo:[:cls |
				   cls isLoaded ifTrue:[
				       (cls isNameSpace not or:[cls == Smalltalk]) ifTrue:[
					   (nameSpaceFilter isNil
					   or:[self isClass:cls shownWithNameSpaceFilter:nameSpaceFilter]) ifTrue:[
					       (packageFilter isNil
					       or:[self isClass:cls shownWithPackageFilter:packageFilter]) ifTrue:[
						   whatToDo value:cls
					       ]
					   ]
				       ]
				   ]
			       ]
			  ]
	].
	^ Iterator on:[:whatToDo | 
			   Smalltalk allClassesDo:[:cls |
			       (cls isNameSpace not or:[cls == Smalltalk]) ifTrue:[
				    (nameSpaceFilter isNil
				    or:[self isClass:cls shownWithNameSpaceFilter:nameSpaceFilter]) ifTrue:[
					(packageFilter isNil
					or:[self isClass:cls shownWithPackageFilter:packageFilter]) ifTrue:[
					    whatToDo value:cls
					]
				    ]
			       ]
			   ]
		      ]
    ].

    ^ Iterator on:[:whatToDo | 
		       Smalltalk allClassesDo:[:cls |
			   (hideUnloadedClasses not or:[cls isLoaded]) 
			   ifTrue:[
			       (cls isNameSpace not or:[cls == Smalltalk]) ifTrue:[
				   (cats includes:cls category) ifTrue:[
				       (nameSpaceFilter isNil
				       or:[self isClass:cls shownWithNameSpaceFilter:nameSpaceFilter]) ifTrue:[
					   (packageFilter isNil
					   or:[self isClass:cls shownWithPackageFilter:packageFilter]) ifTrue:[
					       whatToDo value:cls
					   ]
				       ]
				   ]
			       ]
			   ]
		       ]
		  ]

    "Created: / 5.2.2000 / 13:42:12 / cg"
    "Modified: / 18.8.2000 / 15:52:41 / cg"
! !

!ClassCategoryList methodsFor:'private'!

colorizeCategoryAsChanged:category
    |colorizedCategoryItem categoryList idx|

    colorizedCategoryItem := self colorizeForChangedCode:category copy asText.

    categoryList := self categoryList value.
    idx := categoryList indexOf:category.
    idx ~~ 0 ifTrue:[
	((categoryList at:idx) sameStringAndEmphasisAs:colorizedCategoryItem) ifFalse:[
	    categoryList at:idx put:colorizedCategoryItem.
	    self categoryList changed.
	]
    ].
!

listOfCategories
    |categories hideUnloadedClasses generator nameSpaceFilter packageFilter allName
     categoriesWithExtensions categoriesWithChangedCode classesInCangeSet|

    allName := self class nameListEntryForALL.

    hideUnloadedClasses := self hideUnloadedClasses value.
    nameSpaceFilter := self nameSpaceFilter value.
    nameSpaceFilter notNil ifTrue:[
	(nameSpaceFilter includes:allName) ifTrue:[nameSpaceFilter := nil].
    ].
    packageFilter := self packageFilter value.
    packageFilter notNil ifTrue:[
	(packageFilter includes:allName) ifTrue:[packageFilter := nil].
    ].

    categories := Set new.
    categoriesWithExtensions := Set new.
    categoriesWithChangedCode := Set new.

    classesInCangeSet := ChangeSet current changedClasses.
    classesInCangeSet := classesInCangeSet collect:[:eachClass | eachClass theNonMetaclass].

    classes := IdentitySet new.
    inGeneratorHolder isNil ifTrue:[
	Smalltalk allClassesDo:[:cls | 
	    |cat|

	    (hideUnloadedClasses not or:[cls isLoaded])
	    ifTrue:[
		(cls isNameSpace not
		or:[cls == Smalltalk]) ifTrue:[
		    (nameSpaceFilter isNil
		    or:[self isClass:cls shownWithNameSpaceFilter:nameSpaceFilter]) ifTrue:[
			(packageFilter isNil
			or:[self isClass:cls shownWithPackageFilter:packageFilter]) ifTrue:[
			    cat := cls category.
			    cat isString ifFalse:[self halt:'oops - strange category'].
			    categories add:cat.
			    classes add:cls.

			    (classesInCangeSet includes:cls theNonMetaclass) ifTrue:[
				categoriesWithChangedCode add:cat
			    ] ifFalse:[
				cls hasExtensions ifTrue:[
				    categoriesWithExtensions add:cat
				]
			    ].
			]
		    ]
		]
	    ]
	].

	"/ those are simulated - in ST/X, empty categories do not
	"/ really exist; however, during browsing, it makes sense.
	AdditionalEmptyCategories size > 0 ifTrue:[
	    "/ remove those that are present ...
	    AdditionalEmptyCategories := AdditionalEmptyCategories select:[:cat | (categories includes:cat) not].
	    categories addAll:AdditionalEmptyCategories.
	].
    ] ifFalse:[
	generator := inGeneratorHolder value.
	generator isNil ifTrue:[^ #() ].
	generator do:[:cat | categories add:cat].
    ].

    categories := categories asOrderedCollection.

    categories sort.
    categories := categories collect:[:cat | 
					    (categoriesWithChangedCode includes:cat) ifTrue:[
						 (self colorizeForChangedCode:cat copy asText).
						 "/ cannot add a + here - need separate list for presentation and filter
					    ] ifFalse:[
						(categoriesWithExtensions includes:cat) ifTrue:[
						     (self colorizeForDifferentPackage:cat copy asText)
						     "/ cannot add a + here - need separate list for presentation and filter
						] ifFalse:[
						     cat
						]
					    ]
				     ].
    categories size == 1 ifTrue:[
	self classCategoryLabelHolder value:(categories first)
    ].
    categories size == 0 ifFalse:[
	categories addFirst:(self class nameListEntryForALL).
    ].
    ^ categories

    "Created: / 5.2.2000 / 13:42:12 / cg"
    "Modified: / 13.11.2001 / 11:32:36 / cg"
!

listView 
    ^ self builder componentAt:#List
!

makeDependent
    Smalltalk addDependent:self.
    ChangeSet addDependent:self.

    "Created: / 5.2.2000 / 13:42:13 / cg"
!

makeIndependent
    Smalltalk removeDependent:self.
    ChangeSet removeDependent:self.

    "Created: / 5.2.2000 / 13:42:13 / cg"
!

makeItemVisible:item
    |idx listView|

    idx := categoryList value indexOf:item.
    idx ~~ 0 ifTrue:[
	(listView := self listView) notNil ifTrue:[
	    listView makeLineVisible:idx.
	]
    ]
!

makeSelectionOtherThanAllVisible
    |selectedCategories item|

    selectedCategories := self selectedCategoriesStrings.
    "/ the first item after the *all* item
    item := (selectedCategories copy remove:self class nameListEntryForALL; yourself) first.
    self makeItemVisible:item.
!

release
    super release.

    categoryList removeDependent:self.
!

selectedCategoriesStrings
    |selectedCategoriesHolder selectedCategories|

    selectedCategoriesHolder := self selectedCategories.
    selectedCategories := selectedCategoriesHolder value ? #().
    selectedCategories := selectedCategories collect:[:each | each string].
    ^ selectedCategories
!

updateList
    |oldList newList oldSelection newSelection prevClasses
     selectedCategoriesHolder|

    selectedCategoriesHolder := self selectedCategories.
    oldSelection := selectedCategoriesHolder value ? #().
    prevClasses := classes copy.

    newList := self listOfCategories.
    oldList := (self categoryList value) ? #().
    (newList sameContentsAs:oldList whenComparedWith:[:a :b | a sameStringAndEmphasisAs: b]) 
    ifFalse:[
	oldSelection size > 0 ifTrue:[
	    selectedCategoriesHolder removeDependent:self.
	    selectedCategoriesHolder value:#().
	    selectedCategoriesHolder addDependent:self.
	].
	categoryList value:newList.

	oldSelection size > 0 ifTrue:[
	    newSelection := oldSelection select:[:cat | newList includes:cat].
	    selectedCategoriesHolder value:newSelection.
	]
    ] ifTrue:[
	"/ in case the same categories are present, but classes have changed ...
	(prevClasses isNil or:[(classes identicalContentsAs:prevClasses) not]) ifTrue:[
	    self updateOutputGenerator.
	]
    ].
    listValid := true.

    "Created: / 5.2.2000 / 13:42:13 / cg"
    "Modified: / 18.8.2000 / 15:52:22 / cg"
! !

!ClassCategoryList methodsFor:'setup'!

commonPostBuildWith:aBuilder
    |listView|

    listView := self listView.
    listView notNil ifTrue:[
	listView scrollWhenUpdating:nil
    ].
    super commonPostBuildWith:aBuilder
! !

!ClassCategoryList methodsFor:'special'!

addAdditionalCategory:aCategory
    "/ those are simulated - in ST/X, empty categories do not
    "/ really exist; however, during browsing, it makes sense.
    AdditionalEmptyCategories isNil ifTrue:[
	AdditionalEmptyCategories := Set new.
    ].
    AdditionalEmptyCategories add:aCategory.

    Smalltalk changed:#organization   "/ not really ... to force update
!

removeAdditionalCategories:aListOfCategories

    "/ those are simulated - in ST/X, empty categories do not
    "/ really exist; however, during browsing, it makes sense.
    AdditionalEmptyCategories isNil ifTrue:[^ self].
    aListOfCategories do:[:eachCategory |
	AdditionalEmptyCategories remove:eachCategory ifAbsent:nil.
    ].

    Smalltalk changed:#organization   "/ not really ... to force update
!

removeAllAdditionalCategories
    "/ those are simulated - in ST/X, empty categories do not
    "/ really exist; however, during browsing, it makes sense.
    AdditionalEmptyCategories := nil
! !

!ClassCategoryList class methodsFor:'documentation'!

version
    ^ '$Header: /cvs/stx/stx/libtool/Tools_ClassCategoryList.st,v 1.2 2004-02-26 19:03:55 cg Exp $'
! !