'From Cuis 1.0 of 4 September 2009 [latest update: #290] on 21 September 2009 at 11:45:09 am'!
!classDefinition: #NewParagraph category: #'System-Text'!
Object subclass: #NewParagraph
	instanceVariableNames: 'text container lines positionWhenComposed maxRightX selectionStart selectionStop focused editor '
	classVariableNames: ''
	poolDictionaries: ''
	category: 'System-Text'!
!classDefinition: #TextComposer category: #'Morphic-Text Support'!
Object subclass: #TextComposer
	instanceVariableNames: 'lines maxRightX currentY scanner possibleSlide nowSliding prevIndex prevLines currCharIndex startCharIndex stopCharIndex deltaCharIndex theText theContainer isFirstLine defaultLineHeight actualHeight anythingComposed editor '
	classVariableNames: ''
	poolDictionaries: ''
	category: 'Morphic-Text Support'!

!Canvas methodsFor: 'drawing' stamp: 'jmv 9/21/2009 09:42'!
paragraph: paragraph bounds: bounds color: c
	"Draw the given paragraph"
	^self subclassResponsibility! !


!ColorMappingCanvas methodsFor: 'drawing' stamp: 'jmv 9/21/2009 09:42'!
paragraph: paragraph bounds: bounds color: c
	"Draw the given paragraph"
	myCanvas
		paragraph: paragraph
		bounds: bounds
		color: (self mapColor: c)! !


!FormCanvas methodsFor: 'drawing' stamp: 'jmv 9/21/2009 09:41'!
paragraph: para bounds: bounds color: c
	| scanner |
	self setPaintColor: c.
	scanner _ (port clippedBy: (bounds translateBy: origin))
		displayScannerFor: para
		foreground: (self shadowColor ifNil: [c])
		ignoreColorChanges: self shadowColor notNil.
	para
		displayOn: (self copyClipRect: bounds)
		using: scanner
		at: origin+ bounds topLeft! !


!NewParagraph methodsFor: 'access' stamp: 'jmv 9/21/2009 09:19'!
editor: anEditor
	"Can be nil.
	But if not nil, must be the same editor used in any TextMorph"
	editor _ anEditor! !

!NewParagraph methodsFor: 'composition' stamp: 'jmv 9/21/2009 10:37'!
composeLinesFrom: start to: stop delta: delta into: lineColl priorLines: priorLines atY: startingY 
	"While the section from start to stop has changed, composition may ripple all the way to the end of the text.  However in a rectangular container, if we ever find a line beginning with the same character as before (ie corresponding to delta in the old lines), then we can just copy the old lines from there to the end of the container, with adjusted indices and y-values"

	| newResult composer |
	composer _ TextComposer new.
	editor ifNotNil: [
		composer editor: editor ].
	newResult := composer
				composeLinesFrom: start
				to: stop
				delta: delta
				into: lineColl
				priorLines: priorLines
				atY: startingY
				text: text
				container: container.
	lines := newResult first asArray.
	maxRightX := newResult second.
	^maxRightX! !

!NewParagraph methodsFor: 'display' stamp: 'jmv 9/21/2009 09:40'!
displaySelectionInLine: line on: aCanvas
	| leftX rightX idx caretFont t b caretAttributes |
	selectionStart ifNil: [^self].	"No selection"
	selectionStart = selectionStop 
		ifTrue: [
			"Only show caret on line where clicked"
			selectionStart textLine ~= line ifTrue: [^self]
			]
		ifFalse: [
			"Test entire selection before or after here"
			(selectionStop stringIndex < line first 
				or: [selectionStart stringIndex > (line last + 1)])
					ifTrue: [^self].	"No selection on this line"
			(selectionStop stringIndex = line first 
				and: [selectionStop textLine ~= line])
					ifTrue: [^self].	"Selection ends on line above"
			(selectionStart stringIndex = (line last + 1) 
				and: [selectionStop textLine ~= line])
					ifTrue: [^self]].	"Selection begins on line below"
	leftX _ (selectionStart stringIndex < line first 
				ifTrue: [ line ]
				ifFalse: [ selectionStart ]) left.
	rightX _ (selectionStop stringIndex > (line last + 1) or: [
					selectionStop stringIndex = (line last + 1) 
						and: [selectionStop textLine ~= line]]) 
				ifTrue: [line right]
				ifFalse: [selectionStop left].
	selectionStart = selectionStop 
		ifTrue: [
			idx _ selectionStart stringIndex.
			caretAttributes _ editor ifNotNil: [ editor currentAttributes ].
			caretFont _ caretAttributes
				ifNil: [ text fontAt: idx ]
				ifNotNil: [ text fontIfApplying: caretAttributes ].
			b _ line top + line baseline + caretFont descent.
			t _ line top + line baseline - caretFont ascent.
			self
				displayInsertionMarkAtX: leftX
				top: t
				bottom: b
				width: self caretWidth
				emphasis: caretFont emphasis
				on: aCanvas
			]
		ifFalse: [
			aCanvas fillRectangle: (leftX @ line top corner: rightX @ line bottom)
				color: self selectionColor]! !

!NewParagraph methodsFor: 'editing' stamp: 'jmv 9/21/2009 09:39'!
clickAt: clickPoint for: model
	"Give sensitive text a chance to fire.  Display flash: (100@100 extent: 100@100)."
	| startBlock action target range boxes box |
	action _ false.
	startBlock _ self characterBlockAtPoint: clickPoint.
	(text attributesAt: startBlock stringIndex) do: [ :att | 
		att mayActOnClick ifTrue:
				[(target _ model) ifNil: [ target _ editor morph].
				range _ text rangeOf: att startingAt: startBlock stringIndex.
				boxes _ self selectionRectsFrom: (self characterBlockForIndex: range first) 
							to: (self characterBlockForIndex: range last+1).
				box _ boxes detect: [:each | each containsPoint: clickPoint] ifNone: [nil].
				box ifNotNil: [
					box _ (editor transformFrom: nil) invertBoundsRect: box.
					editor morph allOwnersDo: [ :m | box _ box intersect: (m boundsInWorld) ].
					Utilities awaitMouseUpIn: box
						repeating: []
						ifSucceed: [(att actOnClickFor: target in: self at: clickPoint editor: editor) ifTrue: [action _ true]].
					Cursor currentCursor == Cursor webLink ifTrue:[Cursor normal show].
				]]].
	^ action! !

!NewParagraph methodsFor: 'fonts-display' stamp: 'jmv 9/21/2009 09:41'!
displayOn: aCanvas using: displayScanner at: somePosition
	"Send all visible lines to the displayScanner for display"
	| visibleRectangle offset leftInRun line |
	visibleRectangle _ aCanvas clipRect.
	offset _ (somePosition - positionWhenComposed) truncated.
	leftInRun _ 0.
	(self lineIndexForPoint: visibleRectangle topLeft)
		to: (self lineIndexForPoint: visibleRectangle bottomRight)
		do: [:i | line _ lines at: i.
			self displaySelectionInLine: line on: aCanvas.
			line first <= line last ifTrue: [
				leftInRun _ displayScanner displayLine: line offset: offset leftInRun: leftInRun]].
! !


!TextComposer methodsFor: 'as yet unclassified' stamp: 'jmv 9/21/2009 10:40'!
addNullLineWithIndex: index andRectangle: r
	"The line to add is always the first line of a new paragraph"
	| ts f h bs |
	editor
	ifNotNil: [
		ts _ editor lastParagraphStyle.
		f _ editor lastFont.
		h _ f height + ts paragraphSpacingBefore + ts paragraphSpacingAfter.
		bs _ f ascent + ts paragraphSpacingBefore.
		]
	ifNil: [
		f _ theText fontAt: theText size + 1.
		h _ f height.
		bs _ f ascent.
	].
	lines addLast: (
		(
			TextLine 
				start: index 
				stop: index - 1
				internalSpaces: 0 
				paddingWidth: 0
		)
			rectangle: r;
			lineHeight: h baseline: bs
	)! !

!TextComposer methodsFor: 'accessing' stamp: 'jmv 9/21/2009 10:37'!
editor: anEditor
	editor _ anEditor! !


!TextEditor methodsFor: 'accessing' stamp: 'jmv 9/21/2009 10:31'!
currentAttributes
	| i t |
	i _ self startIndex.
	t _ paragraph text.
	^i > t size
		ifTrue: [
			emphasisHere ]
		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 ]) ]! !

!TextEditor methodsFor: 'accessing' stamp: 'jmv 9/21/2009 09:55'!
currentCharacterStyleOrNil
	"Answer the CharacterStyle for the current selection or cursor location if any"

	^self hasSelection
		ifTrue: [ paragraph text characterStyleOrNilAt: self startIndex ]
		ifFalse: [ paragraph text characterStyleOrNilIfApplying: emphasisHere ]! !

!TextEditor methodsFor: 'accessing' stamp: 'jmv 9/21/2009 10:01'!
currentParagraphStyle
	"Answer the TextStyle for the current selection or cursor location if any"
	
	| i t |
	i _ self startIndex.
	t _ paragraph text.
	^i > t size
		ifTrue: [ paragraph text styleIfApplying: emphasisHere ]
		ifFalse: [ paragraph text textStyleAt: i ]! !

!TextEditor methodsFor: 'accessing' stamp: 'jmv 9/21/2009 10:42'!
lastFont
	"Answer the Font for to be used if positioned at the end of the text"
	
	| t |
	t _ paragraph text.
	^self startIndex > t size
		ifTrue: [ paragraph text fontIfApplying: emphasisHere ]
		ifFalse: [ paragraph text fontAt: t size +1 ]! !

!TextEditor methodsFor: 'accessing' stamp: 'jmv 9/21/2009 10:42'!
lastParagraphStyle
	"Answer the TextStyle for to be used if positioned at the end of the text"
	
	| t |
	t _ paragraph text.
	^self startIndex > t size
		ifTrue: [ paragraph text styleIfApplying: emphasisHere ]
		ifFalse: [ paragraph text textStyleAt: t size +1 ]! !

!TextEditor methodsFor: 'events' stamp: 'jmv 9/21/2009 09:39'!
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) 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/21/2009 09:25'!
changeParagraph: aParagraph 
	"Install aParagraph as the one to be edited by the receiver."

	UndoParagraph == paragraph ifTrue: [UndoParagraph _ nil].
	paragraph _ aParagraph.
	paragraph editor: self.
	self resetState! !

!TextEditor methodsFor: 'private' stamp: 'jmv 9/21/2009 11:25'!
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 |

	emphasisHere _ Text addAttribute: aTextAttribute toArray: emphasisHere.

	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 isEmpty
				ifTrue: [ paragraph composeAll ]
				ifFalse: [
					self replaceSelectionWith: (thisSel addAttribute: aTextAttribute).
					paragraph composeAll.
					self recomputeSelection] ].

	morph possiblyChanged ! !

!TextEditor methodsFor: 'private' stamp: 'jmv 9/21/2009 11:26'!
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 |

	emphasisHere _ emphasisHere copyWithout: aTextAttribute.

	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 isEmpty
				ifTrue: [ paragraph composeAll ]
				ifFalse: [
					self replaceSelectionWith: (thisSel removeAttribute: aTextAttribute).
					paragraph composeAll.
					self recomputeSelection] ].
	morph possiblyChanged ! !


!TextMorph methodsFor: 'drawing' stamp: 'jmv 9/21/2009 09:42'!
drawOn: aCanvas
	"Draw the receiver on a canvas"

	| fauxBounds |
	self setDefaultContentsIfNil.
	super drawOn: aCanvas.  "Border and background if any"
	false ifTrue: [self debugDrawLineRectsOn: aCanvas].  "show line rects for debugging"
	text size = 0
		ifTrue: [self drawNullTextOn: aCanvas].
	"Hack here:  The canvas expects bounds to carry the location of the text, but we also need to communicate clipping."
	fauxBounds _ self bounds topLeft corner: self innerBounds bottomRight.
	aCanvas paragraph: self paragraph bounds: fauxBounds color: color! !

!TextMorph methodsFor: 'private' stamp: 'jmv 9/21/2009 09:23'!
releaseParagraph

	"Paragraph instantiation is lazy -- it will be created only when needed"
	"release editor too"
	editor ifNotNil: [
		self selectionChanged.
		self paragraph selectionStart: nil selectionStop: nil.	"most likely not needed"
		editor _ nil].
	paragraph ifNotNil: [
		paragraph _ nil]
! !


!TextMorphForEditView methodsFor: 'drawing' stamp: 'jmv 9/21/2009 09:42'!
drawNullTextOn: aCanvas
	"Just run the normal code to show selection in a window"
	aCanvas paragraph: self paragraph bounds: bounds color: color! !


!Transcripter methodsFor: 'accessing' stamp: 'jmv 9/21/2009 09:42'!
endEntry
	| c d cb |
	c _ self contents.
	Display extent ~= DisplayScreen actualScreenSize ifTrue: [
		"Handle case of user resizing physical window"
		DisplayScreen startUp.
		frame _ frame intersect: Display boundingBox.
		^ self clear; show: c].
	para compose: c asText
		in: ((frame insetBy: 4) withHeight: 9999).
	para positionWhenComposed: 0@0.
	d _ para extent y - frame height.
	d > 0 ifTrue: [
		"Scroll up to keep all contents visible"
		cb _ para characterBlockAtPoint:
			para compositionRectangle topLeft + (0@(d+TextStyle default initialLineGrid)).
		self on: (c copyFrom: cb stringIndex to: c size).
		readLimit_ position_ collection size.
		^ self endEntry].
	Display fill: (frame insetBy: -2) fillColor: self black;
			fill: frame fillColor: self white.
	Display getCanvas
		paragraph: para 
		bounds: (0@0 extent: Display extent) 
		color: Color black! !

TextMorph removeSelector: #releaseEditor!
!classDefinition: #TextComposer category: #'Morphic-Text Support'!
Object subclass: #TextComposer
	instanceVariableNames: 'lines maxRightX currentY scanner possibleSlide nowSliding prevIndex prevLines currCharIndex startCharIndex stopCharIndex deltaCharIndex theText theContainer isFirstLine defaultLineHeight actualHeight anythingComposed editor'
	classVariableNames: ''
	poolDictionaries: ''
	category: 'Morphic-Text Support'!
NewParagraph removeSelector: #clickAt:for:controller:!
NewParagraph removeSelector: #displayOn:using:at:editor:!
NewParagraph removeSelector: #displaySelectionInLine:on:editor:!
!classDefinition: #NewParagraph category: #'System-Text'!
Object subclass: #NewParagraph
	instanceVariableNames: 'text container lines positionWhenComposed maxRightX selectionStart selectionStop focused editor'
	classVariableNames: ''
	poolDictionaries: ''
	category: 'System-Text'!
FormCanvas removeSelector: #paragraph:bounds:color:editor:!
ColorMappingCanvas removeSelector: #paragraph:bounds:color:editor:!
Canvas removeSelector: #paragraph:bounds:color:editor:!
