'From Cuis 1.0 of 2 October 2009 [latest update: #316] on 7 October 2009 at 12:32:04 pm'!

!AlignmentMorph methodsFor: 'construction' stamp: 'jmv 10/4/2009 15:03'!
addInRow: morphs atFractions: fractions
	"Please ensure:
	fractions size = (morphs size + 1)
	fractions first = 0.0
	fractions last = 1.0"

	morphs doWithIndex: [ :morph :i |
		self
			addMorph: morph
			fullFrame: (LayoutFrame
				fractions: ((fractions at: i)@0 corner: (fractions at: i + 1)@1)) ]! !


!LazyListMorph methodsFor: 'drawing' stamp: 'jmv 10/1/2009 10:04'!
adjustWidth
	"private.  Adjust our height to match the length of the underlying list,
	but make it wider if neccesary to fill the available width in our PluggableListMorph"
	self width: (listSource viewableWidth max: self hTotalScrollRange)! !

!LazyListMorph methodsFor: 'scroll range' stamp: 'jmv 10/1/2009 09:47'!
hTotalScrollRange
"Ok, this is a bit messed up. We need to return the width of the widest item in the list. If we grab every item in the list, it defeats the purpose of LazyListMorph. If we don't, then we don't know the size. This is a compromise -- if the list is less then 30 items, we grab them all. If not, we grab currently visible ones, until we've checked itemsToCheck of them, then take the max width out of that 'sampling', then double it. If you know a better way, please chime in."

	| maxW count itemsToCheck item |

	itemsToCheck _ 30.
	maxW _ 0. 
	count _ 0.
	listItems do: 
		[ :each |
			each ifNotNil: 
				[maxW _ maxW max: (self widthToDisplayItem: each contents)]].
				
	(count < itemsToCheck) ifTrue:
		[1 to: listItems size do: 
			[:i | (listItems at: i) ifNil: 
							[item _ self item: i.
							maxW _ maxW max: (self widthToDisplayItem: item contents).
							((count _ count + 1) > itemsToCheck) ifTrue:[ ^maxW * 2]]]].
	
	^maxW 
! !


!ScrollBar methodsFor: 'model access' stamp: 'jmv 10/1/2009 09:44'!
setValue: newValue
	"Called internally for propagation to model"
	self value: newValue.
	self use: setValueSelector orMakeModelSelectorFor: 'Value:'
		in: [:sel | setValueSelector _ sel.  model perform: sel with: value]! !


!ScrollPane methodsFor: 'geometry' stamp: 'jmv 10/1/2009 10:22'!
focusIndicatorRectangle

	| b topLeft bottomRight |
	b _ self innerBounds.
	topLeft _ b topLeft .
	bottomRight _ b bottomRight.
	self vIsScrollbarShowing ifTrue: [
		scrollBarOnLeft
			ifTrue: [ topLeft _ scrollBar right @ topLeft y]
			ifFalse: [ bottomRight _ scrollBar left @ bottomRight y]].
	self hIsScrollbarShowing
		ifTrue: [ bottomRight _ bottomRight x @ (bottomRight y - Preferences scrollbarThickness)].
	^topLeft corner: bottomRight! !

!ScrollPane methodsFor: 'geometry' stamp: 'jmv 10/1/2009 08:40'!
hLeftoverScrollRange
	"Return the entire scrolling range minus the currently viewed area."

	scroller hasSubmorphs ifFalse: [^0].
	^ (self hTotalScrollRange - self viewableWidth) max: 0! !

!ScrollPane methodsFor: 'geometry' stamp: 'jmv 9/30/2009 13:01'!
hScrollBarWidth
	"Return the width of the horizontal scrollbar"

	| w |	
	w _ bounds width - (2 * borderWidth).
	self vIsScrollbarShowing
		ifTrue: [ w _ w - Preferences scrollbarThickness ].
	^w! !

!ScrollPane methodsFor: 'geometry' stamp: 'jmv 10/1/2009 10:30'!
hSetScrollDelta
	"Set the ScrollBar deltas, value and interval, based on the current scroll pane size, offset and range."
	| range delta w |

	scroller hasSubmorphs ifFalse:[scrollBar interval: 1.0. ^self].
	
	delta _ self scrollDeltaWidth.
	range _ self hLeftoverScrollRange.
	range = 0 ifTrue: [ hScrollBar scrollDelta: 0.02 pageDelta: 0.2; interval: 1.0; setValue: 0. ^self].

	"Set up for one line (for arrow scrolling), or a full pane less one line (for paging)."
	w _ self viewableWidth.
	hScrollBar scrollDelta: (delta / range) asFloat pageDelta: (w - delta / range) asFloat.
	hScrollBar interval: (w / self hTotalScrollRange) asFloat.
	hScrollBar setValue: ((scroller offset x / range) min: 1.0) asFloat.
! !

!ScrollPane methodsFor: 'geometry' stamp: 'jmv 10/1/2009 09:47'!
hTotalScrollRange
	"Return the width extent of the receiver's submorphs."

	| submorphBounds |
	submorphBounds := scroller localSubmorphBounds ifNil: [^ 0].
	^ submorphBounds right
! !

!ScrollPane methodsFor: 'geometry' stamp: 'jmv 9/30/2009 12:43'!
mightNeedHorizontalScrollBar
	"If not sure, answer true. Only answer false when the horizontal scrollbar will not be needed, regardless of contents, for example, when showing wrapped text.
	This method is called for deciding if a vertical scroll bar is needed. Therefore if the need of an horizontal scrollbar might depend on the vertical scrollbar being there (and taking space) or not, just answer true."
	^true! !

!ScrollPane methodsFor: 'geometry' stamp: 'jmv 10/7/2009 12:30'!
resizeScroller

	scroller bounds: self viewableBounds! !

!ScrollPane methodsFor: 'geometry' stamp: 'jmv 10/1/2009 08:41'!
vLeftoverScrollRange
	"Return the entire scrolling range minus the currently viewed area."

	scroller hasSubmorphs ifFalse: [^0].
	^ (self vTotalScrollRange - self viewableHeight) max: 0! !

!ScrollPane methodsFor: 'geometry' stamp: 'jmv 10/1/2009 10:30'!
vSetScrollDelta
	"Set the ScrollBar deltas, value and interval, based on the current scroll pane size, offset and range."
	| range delta h |

	scroller hasSubmorphs ifFalse:[scrollBar interval: 1.0. ^self].
	
	delta _ self scrollDeltaHeight.
	range _ self vLeftoverScrollRange.
	range = 0 ifTrue: [^ scrollBar scrollDelta: 0.02 pageDelta: 0.2; interval: 1.0; setValue: 0].

	"Set up for one line (for arrow scrolling), or a full pane less one line (for paging)."
	h _ self viewableHeight.
	scrollBar scrollDelta: (delta / range) asFloat pageDelta: (h - delta / range) asFloat.
	scrollBar interval: (h / self vTotalScrollRange) asFloat.
	scrollBar setValue: (scroller offset y / range min: 1.0) asFloat.
! !

!ScrollPane methodsFor: 'geometry' stamp: 'jmv 10/1/2009 09:50'!
vTotalScrollRange
	"Return the height extent of the receiver's submorphs."
	| submorphBounds |
	submorphBounds := scroller localSubmorphBounds ifNil: [^ 0].
	^ submorphBounds bottom
! !

!ScrollPane methodsFor: 'geometry' stamp: 'jmv 10/1/2009 10:25'!
viewableBounds

	^self focusIndicatorRectangle insetBy: self xtraBorder! !

!ScrollPane methodsFor: 'geometry' stamp: 'jmv 9/30/2009 11:56'!
viewableHeight
	"Viewable height.
	Leave room for horizontal scrollbar if present"

	^self viewableBounds height! !

!ScrollPane methodsFor: 'geometry' stamp: 'jmv 9/30/2009 11:56'!
viewableWidth
	"Viewable width.
	Leave room for vertical scrollbar if present"

	^self viewableBounds width! !

!ScrollPane methodsFor: 'geometry' stamp: 'jmv 10/1/2009 10:19'!
xtraBorder
	"Answer the width of an extra white border to look nicer"
	^3! !

!ScrollPane methodsFor: 'geometry testing' stamp: 'jmv 9/30/2009 12:59'!
hIsScrollbarShowing
	"Return true if a horizontal scroll bar is currently showing"

	^hScrollBar owner == self! !

!ScrollPane methodsFor: 'geometry testing' stamp: 'jmv 9/30/2009 12:59'!
vIsScrollbarShowing
	"Return true if a retractable (or not) vertical scroll bar is currently showing"

	^scrollBar owner == self! !

!ScrollPane methodsFor: 'initialization' stamp: 'jmv 9/30/2009 14:20'!
initializeScrollBars
	"initialize the receiver's scrollBars"

	(scrollBar := ScrollBar new model: self slotName: 'vScrollBar')
		borderWidth: 1;
		borderColor: Color black.
	(hScrollBar := ScrollBar new model: self slotName: 'hScrollBar')
		borderWidth: 1;
		borderColor: Color black.

	scroller := TransformMorph new color: Color transparent.
	scroller offset: 0@ 0.
	self addMorph: scroller
! !

!ScrollPane methodsFor: 'scrolling' stamp: 'jmv 10/7/2009 12:31'!
hHideScrollBar
	self hIsScrollbarShowing ifFalse: [
		^scroller offset: 0@scroller offset y ].
	self removeMorph: hScrollBar.
	scroller offset: 0@scroller offset y.
	retractableScrollBar ifFalse: [ self resetExtent ]! !

!ScrollPane methodsFor: 'scrolling' stamp: 'jmv 10/1/2009 10:19'!
hIsScrollbarNeeded: havingVerticalScrollbar
"Return whether the horz scrollbar is needed"

	"Don't do anything with the retractable scrollbar unless we have focus"
	| w |
	retractableScrollBar & hasFocus not ifTrue: [^false].
	
	"Don't show it if we were told not to."
	hideScrollBars ifTrue: [^false].

	scroller hasSubmorphs ifFalse: [^false].

	"If the contents of the pane are too small to scroll, return false."
	w _ self innerBounds width - (2*self xtraBorder).
	"Compute the need of the horizontal scrollbar honoring the havingVerticalScrollbar argument"
	havingVerticalScrollbar ifTrue: [
		w _ w - Preferences scrollbarThickness ].
	^self hTotalScrollRange > w! !

!ScrollPane methodsFor: 'scrolling' stamp: 'jmv 10/7/2009 12:31'!
hScrollBarValue: scrollValue

	| x |
	self hIsScrollbarShowing ifFalse: [
		^scroller offset: 0@scroller offset y ].
	(x _ self hLeftoverScrollRange * scrollValue) <= 0
		ifTrue: [ x _ 0 ].
	scroller offset: x@scroller offset y! !

!ScrollPane methodsFor: 'scrolling' stamp: 'jmv 9/30/2009 14:41'!
hideOrShowScrollBars

	| wasHShowing wasVShowing useVerticalScrollbar useHorizontalScrollbar |

	wasVShowing _ self vIsScrollbarShowing.
	wasHShowing _ self hIsScrollbarShowing.

	"Assume for a moment we don't need an horizontal scrollbar"
	useHorizontalScrollbar _ false.
	useVerticalScrollbar _ self vIsScrollbarNeeded: useHorizontalScrollbar.
	
	"Avoid all this if we know for sure we won't need an horizontal scrollbar"
	self mightNeedHorizontalScrollBar ifTrue: [
		
		"We are cheating here. May be useVerticalScrollbar is false, and we later need it."
		useHorizontalScrollbar _ self hIsScrollbarNeeded: useVerticalScrollbar.
				
		"If horizontal scrollbar is needed, maybe vertical scrollbar will be needed too."
		(useVerticalScrollbar not and: [ useHorizontalScrollbar ]) ifTrue: [
			
			"In this case, there is no chance of modifying the need of horizontal scrollbar: it was already needed.
			Therefore, there is no circularity here."
			useVerticalScrollbar _ self vIsScrollbarNeeded: useHorizontalScrollbar ]].

	wasVShowing = useVerticalScrollbar ifFalse: [
		useVerticalScrollbar
			ifTrue: [ self vShowScrollBar ]
			ifFalse: [ self vHideScrollBar ]].

	wasHShowing = useHorizontalScrollbar ifFalse: [
		useHorizontalScrollbar
			ifTrue: [ self hShowScrollBar ]
			ifFalse: [ self hHideScrollBar ]]! !

!ScrollPane methodsFor: 'scrolling' stamp: 'jmv 10/7/2009 12:14'!
scrollBy: delta
	"Move the contents in the direction delta."

	| newYoffset r newXoffset |
	
	"Set the offset on the scroller"
	newYoffset _ scroller offset y - delta y max: 0.
	newXoffset _ scroller offset x - delta x max: 0.
	
	scroller offset: newXoffset@ newYoffset.

	"Update the scrollBars"
	(r _ self vLeftoverScrollRange) = 0
		ifTrue: [scrollBar value: 0.0]
		ifFalse: [scrollBar value: newYoffset asFloat / r].
	(r _ self hLeftoverScrollRange) = 0
		ifTrue: [hScrollBar value: 0.0]
		ifFalse: [hScrollBar value: newXoffset asFloat / r]
! !

!ScrollPane methodsFor: 'scrolling' stamp: 'jmv 10/7/2009 12:31'!
scrollToShow: aRectangle
	"scroll to include as much of aRectangle as possible, where aRectangle is in the scroller's local space"
	| range scrollValue |
	((aRectangle top - scroller offset y) >= 0 and: [
		(aRectangle bottom - scroller offset y) <= self viewableHeight ])
		ifTrue: [
			"already visible"
			^self ].

	range _ self vLeftoverScrollRange.
	scrollValue _ range > 0
		ifTrue: [
			(aRectangle top / range
				truncateTo: scrollBar scrollDelta) min: 1]
		ifFalse: [0].
	scrollBar value: scrollValue.
	scroller offset: 0 @ (range * scrollValue).! !

!ScrollPane methodsFor: 'scrolling' stamp: 'jmv 10/1/2009 10:19'!
vIsScrollbarNeeded: havingHorizontalScrollbar
"Return whether the vertical scrollbar is needed"

	"Don't do anything with the retractable scrollbar unless we have focus"
	| h |
	retractableScrollBar & hasFocus not ifTrue: [^false].
	
	"Don't show it if we were told not to."
	hideScrollBars ifTrue: [^false].
	
	scroller hasSubmorphs ifFalse: [^false].	

	"If the contents of the pane are too small to scroll, return false."
	h _ self innerBounds height - (2*self xtraBorder).
	"Compute the need of the vertical scrollbar honoring the havingHorizontalScrollbar argument"
	havingHorizontalScrollbar ifTrue: [
		h _ h - Preferences scrollbarThickness ].
	^ self vTotalScrollRange > h! !

!ScrollPane methodsFor: 'scrolling' stamp: 'jmv 10/7/2009 12:31'!
vScrollBarValue: scrollValue
	scroller hasSubmorphs ifFalse: [^ self].
	scroller offset: (scroller offset x @ (self vLeftoverScrollRange * scrollValue) rounded)
! !

!ScrollPane methodsFor: 'change reporting' stamp: 'jmv 9/30/2009 11:29'!
invalidateBorderFeedback

	| r |
	r _ self focusIndicatorRectangle.
	(r areasOutside: (r insetBy: Preferences focusIndicatorWidth))
		do: [ :rect | self invalidRect: rect ]! !


!PluggableListMorph methodsFor: 'drawing' stamp: 'jmv 9/30/2009 11:29'!
drawOn: aCanvas

	super drawOn: aCanvas.
	self hasKeyboardFocus ifTrue: [
		aCanvas
			frameRectangle: self focusIndicatorRectangle 
			width: Preferences focusIndicatorWidth 
			color: Preferences focusIndicatorColor ]! !

!PluggableListMorph methodsFor: 'geometry' stamp: 'jmv 10/7/2009 12:27'!
extent: newExtent
	super extent: newExtent.
	
	"Change listMorph's bounds to the new width. It is either the size
	of the widest list item, or the size of self, whatever is bigger"
	self listMorph adjustWidth! !

!PluggableListMorph methodsFor: 'scrolling' stamp: 'jmv 10/1/2009 09:48'!
hTotalScrollRange
"Return the width of the widest item in the list"

	^self listMorph hTotalScrollRange
! !

!PluggableListMorph methodsFor: 'scrolling' stamp: 'jmv 10/1/2009 09:50'!
vTotalScrollRange
	"Return the height extent of the receiver's submorphs."
	(scroller submorphs size > 0) ifFalse:[ ^0 ].
	^(scroller submorphs last fullBounds bottom)
! !


!PluggableTextMorph methodsFor: 'drawing' stamp: 'jmv 9/30/2009 11:29'!
drawOn: aCanvas 
	"Include a thin red inset border for unaccepted edits, or, if the unaccepted edits are known to conflict with a change made somewhere else to the same method (typically), put a thick red frame"

	| bw bc |
	super drawOn: aCanvas.
	bw _ Preferences focusIndicatorWidth.
	bc _ nil.
	self wantsFrameAdornments ifTrue: [
		(model notNil and: [model refusesToAcceptCode])
			ifTrue: [  "Put up feedback showing that code cannot be submitted in this state"
				bc _ Color tan]
			ifFalse: [
				self hasEditingConflicts
					ifTrue: [
						bw _ 3.
						bc _ Color red ] 
					ifFalse: [
						self hasUnacceptedEdits
							ifTrue: [
								model wantsDiffFeedback
									ifTrue: [
										bw _ 4.
										bc _ Color green]
									ifFalse: [
										bc _ Color red]]
							ifFalse: [
								model wantsDiffFeedback
									ifTrue: [
										bc _ Color green]
									ifFalse: []]]]].
					
	textMorph hasKeyboardFocus ifTrue: [
		bc ifNil: [
			bc _ Preferences focusIndicatorColor ]]
	ifFalse: [
		bc ifNotNil: [
			bc _ bc alphaMixed: 0.4 with: Color white ]].
	bc notNil ifTrue: [
		aCanvas frameRectangle: self focusIndicatorRectangle width: bw color: bc ]! !

!PluggableTextMorph methodsFor: 'editor access' stamp: 'jmv 10/1/2009 11:08'!
scrollSelectionIntoView: event
	"Scroll my text into view if necessary and return true, else return false"
	| selRects delta selRect rectToTest transform cpHere |
	selectionInterval _ textMorph editor selectionInterval.
	selRects _ textMorph paragraph selectionRects.
	selRects isEmpty ifTrue: [^ false].
	rectToTest _ selRects first merge: selRects last.
	transform _ scroller transformFrom: self.
	(event notNil and: [event anyButtonPressed]) ifTrue: [  "Check for autoscroll"
		cpHere _ transform localPointToGlobal: event cursorPoint.
		cpHere y <= self top
			ifTrue: [rectToTest _ selRects first topLeft extent: 2@2]
			ifFalse: [cpHere y >= self bottom
					ifTrue: [rectToTest _ selRects last bottomRight extent: 2@2]
					ifFalse: [^ false]]].
	selRect _ transform localBoundsToGlobal: rectToTest.
	selRect height > bounds height
		ifTrue: [^ false].  "Would not fit, even if we tried to scroll"
	(delta _ selRect amountToTranslateWithin: self viewableBounds) y ~= 0 ifTrue: [
		"Scroll end of selection into view if necessary"
		self scrollBy: 0@delta y.
		^ true].
	^ false! !

!PluggableTextMorph methodsFor: 'model access' stamp: 'jmv 9/30/2009 11:16'!
basicSetText: aText
	scrollBar setValue: 0.0.
	textMorph 
		ifNil: [
			textMorph _ TextMorphForEditView new
				contents: aText wrappedTo: self viewableWidth.
			textMorph setEditView: self.
			scroller addMorph: textMorph]
		ifNotNil: [textMorph newContents: aText].
	self hasUnacceptedEdits: false.
	self setScrollDeltas.! !

!PluggableTextMorph methodsFor: 'scrolling' stamp: 'jmv 10/1/2009 09:48'!
hTotalScrollRange
"Return the width of the widest item in the list"

	textMorph ifNil: [ ^0 ].
	textMorph isWrapped ifTrue:[ ^0 ].

	^super hTotalScrollRange
! !

!PluggableTextMorph methodsFor: 'scrolling' stamp: 'jmv 9/30/2009 11:46'!
mightNeedHorizontalScrollBar

	textMorph ifNil: [ ^false ].
	textMorph isWrapped ifTrue: [ ^false ].

	^super mightNeedHorizontalScrollBar
! !


!SimpleHierarchicalListMorph methodsFor: 'drawing' stamp: 'jmv 9/30/2009 11:29'!
drawOn: aCanvas

	super drawOn: aCanvas.
	self hasKeyboardFocus ifTrue: [
		aCanvas
			frameRectangle: self focusIndicatorRectangle 
			width: Preferences focusIndicatorWidth
			color: Preferences focusIndicatorColor ].

	selectedMorph ifNotNil:
		[aCanvas fillRectangle:
			(((scroller transformFrom: self) invertBoundsRect: selectedMorph bounds)
						intersect: scroller bounds)
				color: color blacker].

	Preferences showLinesInHierarchyViews ifTrue:[
		self drawLinesOn: aCanvas.
	].


! !

!SimpleHierarchicalListMorph methodsFor: 'scrolling' stamp: 'jmv 10/1/2009 09:47'!
hTotalScrollRange
"Return the width of the widest item in the list"

	| max right stringW count |

	max _ 0.
	count _ 0.
	scroller submorphsDo: [ :each |
		stringW _ each font widthOfStringOrText: each contents.
		right _ (each toggleRectangle right + stringW + 10).
		max _ max max: right.
		
"NOTE: need to optimize this method by caching list item morph widths (can init that cache most efficiently in the #list: method before the item widths are reset to 9999).  For now, just punt on really long lists"
		((count _ count + 1) > 200) ifTrue:[ ^max * 3].
	].

	^max 
! !

!SimpleHierarchicalListMorph methodsFor: 'scrolling' stamp: 'jmv 10/1/2009 09:50'!
vTotalScrollRange
"Return the width of the widest item in the list"

	(scroller submorphs size > 0) ifFalse:[ ^0 ].
	^scroller submorphs last fullBounds bottom
! !

TransformMorph removeSelector: #computeJMV!
TransformMorph removeSelector: #scrollFactorX:!
TransformMorph removeSelector: #scrollFactorY:!
SimpleHierarchicalListMorph removeSelector: #hExtraScrollRange!
SimpleHierarchicalListMorph removeSelector: #hUnadjustedScrollRange!
SimpleHierarchicalListMorph removeSelector: #vUnadjustedScrollRange!
PluggableTextMorph removeSelector: #hUnadjustedScrollRange!
PluggableListMorph removeSelector: #hExtraScrollRange!
PluggableListMorph removeSelector: #hUnadjustedScrollRange!
PluggableListMorph removeSelector: #vUnadjustedScrollRange!
ScrollPane removeSelector: #hExtraScrollRange!
ScrollPane removeSelector: #hHideOrShowScrollBar!
ScrollPane removeSelector: #hIsScrollable!
ScrollPane removeSelector: #hIsScrollbarNeeded!
ScrollPane removeSelector: #hMargin!
ScrollPane removeSelector: #hUnadjustedScrollRange!
ScrollPane removeSelector: #scrollingHeight!
ScrollPane removeSelector: #scrollingWidth!
ScrollPane removeSelector: #vExtraScrollRange!
ScrollPane removeSelector: #vHideOrShowScrollBar!
ScrollPane removeSelector: #vIsScrollable!
ScrollPane removeSelector: #vIsScrollbarNeeded!
ScrollPane removeSelector: #vUnadjustedScrollRange!
LazyListMorph removeSelector: #hUnadjustedScrollRange!
