'From Cuis 1.0 of 4 September 2009 [latest update: #290] on 21 September 2009 at 11:40:53 am'!

!CharacterBlockScanner methodsFor: 'scanning' stamp: 'jmv 9/18/2009 18:40'!
characterBlockAtPoint: aPoint index: index in: textLine
	"This method is the Morphic characterBlock finder.  It combines
	MVC's characterBlockAtPoint:, -ForIndex:, and buildCharcterBlock:in:"
	| runLength lineStop done stopCondition |
	line _ textLine.
	rightMargin _ line rightMargin.
	lastIndex _ line first.
	self setStopConditions.		"also sets font"
	characterIndex _ index.  " == nil means scanning for point"
	characterPoint _ aPoint.
	(characterPoint == nil or: [characterPoint y > line bottom])
		ifTrue: [characterPoint _ line bottomRight].
	(text isEmpty or: [(characterPoint y < line top or: [characterPoint x < line left])
				or: [characterIndex ~~ nil and: [characterIndex < line first]]])
		ifTrue:	[^ (CharacterBlock new stringIndex: line first text: text
					topLeft: line leftMargin@line top extent: 0 @ actualTextStyle initialLineGrid)
					textLine: line].
	destX _ leftMargin _ line leftMarginForAlignment: alignment.
	destY _ line top.
	runLength _ text runLengthFor: line first.
	characterIndex ~~ nil
		ifTrue:	[lineStop _ characterIndex  "scanning for index"]
		ifFalse:	[lineStop _ line last  "scanning for point"].
	runStopIndex _ lastIndex + (runLength - 1) min: lineStop.
	lastCharacterExtent _ 0 @ line lineHeight.
	spaceCount _ 0.

	done  _ false.
	[ done ] whileFalse: [
		stopCondition _ self scanCharactersFrom: lastIndex to: runStopIndex
			in: text string rightX: characterPoint x
			stopConditions: stopConditions kern: kern.
		"see setStopConditions for stopping conditions for character block 	operations."
		self lastCharacterExtentSetX: (specialWidth == nil
			ifTrue: [font widthOf: (text at: lastIndex)]
			ifFalse: [specialWidth]).
		(self perform: stopCondition) ifTrue: [
			characterIndex == nil
				ifTrue: ["Result for characterBlockAtPoint: "
						^ (CharacterBlock new stringIndex: lastIndex
							text: text topLeft: characterPoint + (font descentKern @ 0)
							extent: lastCharacterExtent - (font baseKern @ 0))
									textLine: line]
				ifFalse: ["Result for characterBlockForIndex: "
						^ (CharacterBlock new stringIndex: characterIndex
							text: text topLeft: characterPoint + ((font descentKern - kern max: 0)@ 0)
							extent: lastCharacterExtent)
									textLine: line]]]! !


!Text methodsFor: 'emphasis' stamp: 'jmv 9/18/2009 16:04'!
characterStyleOrNilIfApplying: textAttributes
	"Answer the ParagraphStyle for characters as specified by the argument."
	
	self withAttributeValues: textAttributes do: [ :font :color :alignment :textStyle :characterStyle :kern |
		^characterStyle ]! !

!Text methodsFor: 'emphasis' stamp: 'jmv 9/18/2009 15:40'!
fontIfApplying: textAttributes
	"Answer the font for characters as specified by the argument."
	
	self withAttributeValues: textAttributes do: [ :font :color :alignment :textStyle :characterStyle :kern |
		^font ]! !

!Text methodsFor: 'emphasis' stamp: 'jmv 9/18/2009 15:39'!
styleIfApplying: textAttributes
	"Answer the ParagraphStyle for characters as specified by the argument."
	
	self withAttributeValues: textAttributes do: [ :font :color :alignment :textStyle :characterStyle :kern |
		^textStyle ]! !


!TextEditor methodsFor: 'accessing' stamp: 'jmv 9/21/2009 08:57'!
currentAttributes
	| i t |
	i _ self startIndex.
	t _ paragraph text.
	^i > t size
		ifFalse: [
			"paragraph attributes at the current paragraph will later be applied.
			include them in the answer"
			(emphasisHere reject: [ :attr | attr isParagraphAttribute ]),
				((t attributesAt: i) select: [ :attr | attr isParagraphAttribute ]) ]
		ifTrue: [
			emphasisHere ]! !

!TextEditor methodsFor: 'events' stamp: 'jmv 9/18/2009 15:57'!
mouseDown: evt 
	"An attempt to break up the old processRedButton code into threee phases"
	| clickPoint b |

	oldInterval _ self selectionInterval.
	clickPoint _ evt cursorPoint.
	b _ paragraph characterBlockAtPoint: clickPoint.

	(paragraph clickAt: clickPoint for: model controller: self) ifTrue: [
		self markBlock: b.
		self pointBlock: b.
		evt hand releaseKeyboardFocus: self.
		^ self ].
	
	evt shiftPressed
		ifFalse: [
			self closeTypeIn.
			self markBlock: b.
			self pointBlock: b ]! !

!TextEditor methodsFor: 'initialize-release' stamp: 'jmv 9/18/2009 15:48'!
stateArrayPut: stateArray
	| sel |
	sel _ stateArray at: 1.
	self selectFrom: sel first to: sel last.
	beginTypeInBlock _ stateArray at: 2.
	emphasisHere _ stateArray at: 3.
	morph possiblyChanged! !

!TextEditor methodsFor: 'mvc compatibility' stamp: 'jmv 9/18/2009 15:48'!
zapSelectionWith: aText

	| start stop |
	self deselect.
	start _ self startIndex.
	stop _ self stopIndex.
	(aText isEmpty and: [stop > start]) ifTrue: [
		"If deleting, then set emphasisHere from 1st character of the deletion"
		emphasisHere _ (paragraph text attributesAt: start) select: [:att | att mayBeExtended]].
	(start = stop and: [ aText size = 0 ]) ifFalse: [
		paragraph replaceFrom: start to: stop - 1 with: aText.
		self markIndex: start; pointIndex: start + aText size.
		UndoInterval _ otherInterval _ self selectionInterval.

		self userHasEdited  " -- note text now dirty" ].

	morph possiblyChanged ! !

!TextEditor methodsFor: 'typing support' stamp: 'jmv 9/21/2009 08:58'!
setEmphasisHereFromTextForward: f

	| i t forward delta prevIsSeparator nextIsSeparator |
	i _ self pointIndex.
	t _ paragraph text.
	"Try to set emphasisHere correctly after whitespace.
	Most important after a cr, i.e. at the start of a new line"
	prevIsSeparator _  i > 1 and: [ (t at: i-1) isSeparator ].
	nextIsSeparator _ i <= t size and: [ (t at: i) isSeparator ].
	forward _ prevIsSeparator = nextIsSeparator
		ifTrue: [ f ]
		ifFalse: [ nextIsSeparator ].
	delta _ forward ifTrue: [ 1 ] ifFalse: [ 0 ].
	emphasisHere _ (t attributesAt: (i - delta max: 1))
					select: [:att | att mayBeExtended].
	morph possiblyChanged ! !

!TextEditor methodsFor: 'private' stamp: 'jmv 9/18/2009 18:57'!
applyAttribute: aTextAttribute
	"The user selected aTextAttribute via shortcut, menu or other means.
	If there is a selection, apply the attribute to the selection.
	In any case use the attribute for the user input (emphasisHere)
	It really looks like the 2 branches in this method should be closer!! (handling of Undo is different, and likely wrong)"

	| thisSel interval |

	aTextAttribute isParagraphAttribute
		ifTrue: [
			interval _ paragraph text encompassParagraph: self selectionInterval.
			paragraph 
				replaceFrom: interval first 
				to: interval last 
				with: ((paragraph text copyFrom: interval first to: interval last)
						addAttribute: aTextAttribute).
			paragraph composeAll.
			self recomputeSelection ]
		
		ifFalse: [
			thisSel _ self selection.
			thisSel notEmpty
				ifTrue: [
					self replaceSelectionWith: (thisSel addAttribute: aTextAttribute).
					paragraph composeAll.
					self recomputeSelection] ].

	emphasisHere _ Text addAttribute: aTextAttribute toArray: emphasisHere.
	morph possiblyChanged ! !

!TextEditor methodsFor: 'private' stamp: 'jmv 9/18/2009 15:48'!
unapplyAttribute: aTextAttribute
	"The user selected aTextAttribute to be removed.
	If there is a selection, unapply the attribute to the selection.
	In any case do not use the attribute for the user input (emphasisHere)
	It really looks like the 2 branches in this method should be closer!! (handling of Undo is different, and likely wrong)"

	| thisSel interval |

	aTextAttribute isParagraphAttribute
		ifTrue: [
			interval _ paragraph text encompassParagraph: self selectionInterval.
			paragraph 
				replaceFrom: interval first 
				to: interval last 
				with: ((paragraph text copyFrom: interval first to: interval last)
						removeAttribute: aTextAttribute).
			paragraph composeAll.
			self recomputeSelection ]
		
		ifFalse: [
			thisSel _ self selection.
			thisSel notEmpty
				ifTrue: [
					self replaceSelectionWith: (thisSel removeAttribute: aTextAttribute).
					paragraph composeAll.
					self recomputeSelection] ].

	emphasisHere _ emphasisHere copyWithout: aTextAttribute.
	morph possiblyChanged ! !

!TextEditor methodsFor: 'private' stamp: 'jmv 9/18/2009 15:48'!
unapplyAttributesThat: removalBlock fromWholeParagraph: wholeParagraph
	"The user selected aTextAttribute to be removed.
	If there is a selection, unapply the attribute to the selection.
	In any case do not use the attribute for the user input (emphasisHere)
	It really looks like the 2 branches in this method should be closer!! (handling of Undo is different, and likely wrong)"

	| interval newText secondPass |

	emphasisHere _ emphasisHere reject: removalBlock.
	wholeParagraph
		ifTrue: [
			interval _ paragraph text encompassParagraph: self selectionInterval.
			newText _ paragraph text copyFrom: interval first to: interval last.
			newText
				removeAttributesThat: removalBlock
				replaceAttributesThat: [ :attr | false ]
				by: [ :attr | false ]
				signalIfWholeParagraphAttribute: false.
			paragraph 
				replaceFrom: interval first 
				to: interval last 
				with: newText.
			paragraph composeAll.
			self recomputeSelection ]
		
		ifFalse: [
			newText _ self selection.
			newText notEmpty
				ifTrue: [
					"Do a first pass that will remove only regular attributes (i.e. isParagraphAttribute not)"
					secondPass _ false.
					[
						newText
							removeAttributesThat: removalBlock
							replaceAttributesThat: [ :attr | false ]
							by: [ :attr | false ]
							signalIfWholeParagraphAttribute: true
					] on: Abort do: [ :ex |
						secondPass _ true ].
					self replaceSelectionWith: newText.
					paragraph composeAll.
					self recomputeSelection.
					"Do a second pass, calling this method again, that will remove paragraph attributes from whole paragraphs"
					secondPass ifTrue: [
						self unapplyAttributesThat: [ :attr | attr isParagraphAttribute and: [removalBlock value: attr ]] fromWholeParagraph: true ] ] ].
	morph possiblyChanged ! !


!TextMorphForEditView methodsFor: 'private' stamp: 'jmv 9/18/2009 15:46'!
updateFromParagraph
	super updateFromParagraph.
	editView setScrollDeltas! !

