'From Cuis 2.4 of 17 June 2010 [latest update: #509] on 30 June 2010 at 10:03:09 am'!
!classDefinition: #EmptyLine category: #'Morphic-Text Support'!
TextLine subclass: #EmptyLine
	instanceVariableNames: 'textStyle'
	classVariableNames: ''
	poolDictionaries: ''
	category: 'Morphic-Text Support'!

!EmptyLine commentStamp: '<historical>' prior: 0!
An EmptyLine is a special line that has actually no text. It is used to place the cursor below the last line if it ends with cr. It is also used in case of empty text.!


!CharacterScanner methodsFor: 'scanning' stamp: 'jmv 6/29/2010 10:07'!
plainTab
	"This is the basic method of adjusting destX for a tab."
	destX _ actualTextStyle
				nextTabXFrom: destX
				leftMargin: leftMargin
				rightMargin: rightMargin! !


!CharacterBlockScanner methodsFor: 'stop conditions' stamp: 'jmv 6/29/2010 10:08'!
tab
	| currentX |
	currentX _ actualTextStyle
		nextTabXFrom: destX
		leftMargin: leftMargin
		rightMargin: rightMargin.
	lastSpaceOrTabExtent _ lastCharacterExtent copy.
	self lastSpaceOrTabExtentSetX: (currentX - destX max: 0).
	currentX >= characterPoint x
		ifTrue: 
			[lastCharacterExtent _ lastSpaceOrTabExtent copy.
			^ self crossedX].
	destX _ currentX.
	lastIndex _ lastIndex + 1.
	^false! !


!DisplayScanner methodsFor: 'scanning' stamp: 'jmv 6/29/2010 12:18'!
displayBulletIfAppropriateFor: textLine offset: offset

	actualTextStyle isListStyle
		ifTrue: [
			textLine isFirstLine ifTrue: [
				self displayBulletOffset: offset ]]
		ifFalse: [ lastBulletNumber _ nil ].! !

!DisplayScanner methodsFor: 'scanning' stamp: 'jmv 6/29/2010 12:24'!
displayLine: textLine offset: offset leftInRun: leftInRun
	"The call on the primitive (scanCharactersFrom:to:in:rightX:) will be interrupted according to an array of stop conditions passed to the scanner at which time the code to handle the stop condition is run and the call on the primitive continued until a stop condition returns true (which means the line has terminated).  leftInRun is the # of characters left to scan in the current run; when 0, it is time to call setStopConditions."
	| done stopCondition nowLeftInRun startIndex string lastPos priorFont |

	line _ textLine.
	morphicOffset _ offset.
	lineY _ line top + offset y.
	lineHeight _ line lineHeight.
	rightMargin _ line rightMargin + offset x.
	lastIndex _ line first.
	leftInRun <= 0 ifTrue: [self setStopConditions].
	leftMargin _ (line leftMarginForAlignment: alignment) + offset x.
	destX _ runX _ leftMargin.
	destY _ lineY + line baseline - font ascent.

	textLine isEmptyLine ifTrue: [
		textLine textStyle ifNotNil: [ :ts |
			ts = actualTextStyle ifFalse: [
				""
				foregroundColor _ paragraphColor.
				priorFont _ font.
				self setActualFont: ts font.
				ts color ifNotNil: [ :color | self textColor: color ].
				alignment _ ts alignment.
				actualTextStyle _ ts.
				priorFont ifNotNil: [ destX _ destX + priorFont descentKern ].
				destX _ destX - font descentKern.
				kern _ 0 - font baseKern.
				spaceWidth _ font widthOf: Character space.
				xTable _ font xTable.
				map _ font characterToGlyphMap.
				stopConditions _ DefaultStopConditions.
				font installOn: bitBlt foregroundColor: foregroundColor.
				text ifNotNil:[destY _ lineY + line baseline - font ascent]
				""
			]
		].
		self displayBulletIfAppropriateFor: textLine offset: offset.
		^leftInRun ].

	self displayBulletIfAppropriateFor: textLine offset: offset.

	lastIndex _ line first.
	leftInRun <= 0
		ifTrue: [nowLeftInRun _ text runLengthFor: lastIndex]
		ifFalse: [nowLeftInRun _ leftInRun].
	runStopIndex _ lastIndex + (nowLeftInRun - 1) min: line last.
	spaceCount _ 0.
	done _ false.
	string _ text string.

	[done] whileFalse:[
		startIndex _ lastIndex.
		lastPos _ destX@destY.
		stopCondition _ self scanCharactersFrom: lastIndex to: runStopIndex
						in: string rightX: rightMargin stopConditions: stopConditions
						kern: kern.
		lastIndex >= startIndex ifTrue:[
			font displayString: string on: bitBlt 
				from: startIndex to: lastIndex at: lastPos kern: kern].
		"see setStopConditions for stopping conditions for displaying."
		done _ self perform: stopCondition].
	^ runStopIndex - lastIndex   "Number of characters remaining in the current run"! !


!NewParagraph methodsFor: 'display' stamp: 'jmv 6/30/2010 09:56'!
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"
	selectionStart = selectionStop 
		ifTrue: [
			leftX _ selectionStart left.
			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
				emphasis: caretFont emphasis
				on: aCanvas
			]
		ifFalse: [
			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].
			aCanvas fillRectangle: (leftX @ line top corner: rightX @ line bottom)
				color: self selectionColor]! !

!NewParagraph methodsFor: 'fonts-display' stamp: 'jmv 6/29/2010 12:44'!
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.
			leftInRun _ displayScanner displayLine: line offset: offset leftInRun: leftInRun ]! !


!TextComposer methodsFor: 'as yet unclassified' stamp: 'jmv 6/29/2010 11:04'!
addEmptyTrailingLine
	"The line to add is always the first line of a new paragraph"
	| ts f h bs r lm rm w a leftMarginForAlignment s |
	s _ theText size+1.
	editor
		ifNotNil: [
			ts _ editor lastParagraphStyle.
			f _ editor lastFont.
			h _ f height + ts paragraphSpacingBefore + ts paragraphSpacingAfter.
			bs _ f ascent + ts paragraphSpacingBefore.
			lm _ (ts isListStyle not
				ifTrue: [ts firstIndent]
				ifFalse: [ts restIndent]).
			rm _ ts rightIndent.
			w _ theContainer width.
			a _ ts alignment.
			leftMarginForAlignment _ a = CharacterScanner rightFlushCode
				ifTrue: [ w - rm]
				ifFalse: [
					a = CharacterScanner centeredCode
						ifTrue: [ (w - rm - lm) //2 + lm]
						ifFalse: [ lm ]]]
		ifNil: [
			f _ theText fontAt: s.
			h _ f height.
			bs _ f ascent.
			leftMarginForAlignment _ 0 ].

	r _ theContainer left + leftMarginForAlignment @ currentY extent: 0@h.
	lines addLast: (
		EmptyLine new
			firstIndex: s lastIndex: s - 1;
			rectangle: r;
			lineHeight: h baseline: bs;
			textStyle: ts)! !

!TextComposer methodsFor: 'as yet unclassified' stamp: 'jmv 6/29/2010 12:17'!
composeLinesFrom: argStart to: argStop delta: argDelta into: argLinesCollection priorLines: argPriorLines atY: argStartY text: argText container: argContainer

	lines _ argLinesCollection.
	theText _ argText.
	theContainer _ argContainer.
	deltaCharIndex _ argDelta.
	currCharIndex _ startCharIndex _ argStart.
	stopCharIndex _ argStop.
	prevLines _ argPriorLines.
	currentY _ argStartY.
	defaultLineHeight _ theText initialStyle initialLineGrid.
	maxRightX _ theContainer left.
	possibleSlide _ stopCharIndex < theText size and: [theContainer isMemberOf: Rectangle].
	nowSliding _ false.
	prevIndex _ 1.
	scanner _ CompositionScanner new text: theText.
	isFirstLine _ currCharIndex = 1 or: [
		 (theText at: currCharIndex-1) = Character cr].
	anythingComposed _ false.
	self composeAllLines.
	(anythingComposed not or: [ theText last = Character cr ])
		ifTrue: [
			self addEmptyTrailingLine ].
	^{lines asArray. maxRightX}

! !


!TextEditor methodsFor: 'typing/selecting keys' stamp: 'jmv 6/29/2010 10:28'!
crWithIndent: aKeyboardEvent

	"Only for SmalltalkEditor. Regular editors don't indent"
	^ self normalCharacter: aKeyboardEvent ! !


!SmalltalkEditor methodsFor: 'typing/selecting keys' stamp: 'jmv 6/29/2010 10:24'!
crWithIndent: aKeyboardEvent
	"Replace the current text selection with CR followed by as many tabs
	as there are leading tabs on the current line (+/- bracket count)."
	| char s i tabCount stopIndex |
	s _ paragraph string.
	stopIndex _ self stopIndex.
	i _ stopIndex.
	tabCount _ 0.
	[ (i _ i-1) > 0 and: [ (char _ s at: i) ~= Character cr ] ] whileTrue: [
		"Count brackets"
		char = $[ ifTrue: [tabCount _ tabCount + 1].
		char = $] ifTrue: [tabCount _ tabCount - 1]].
	[ (i _ i + 1) < stopIndex and: [ (char _ s at: i) isSeparator ] ] whileTrue: [
		"Count leading tabs"
		char = Character tab ifTrue: [ tabCount _ tabCount + 1 ]].
	"Now inject CR with tabCount tabs"
	self addString: (String streamContents: [ :strm | strm crtab: tabCount ]).
	^ false! !


!TextLine methodsFor: 'accessing' stamp: 'jmv 6/28/2010 13:59'!
leftMarginForAlignment: alignmentCode
	alignmentCode = CharacterScanner rightFlushCode ifTrue: [^ self left + paddingWidth].
	alignmentCode = CharacterScanner centeredCode ifTrue: [
		^ self left + (paddingWidth//2)].
	^ self left  "leftFlush and justified"! !

!TextLine methodsFor: 'testing' stamp: 'jmv 6/29/2010 10:55'!
isEmptyLine
	^false! !


!EmptyLine methodsFor: 'accessing' stamp: 'jmv 6/29/2010 11:02'!
textStyle

	^textStyle! !

!EmptyLine methodsFor: 'accessing' stamp: 'jmv 6/29/2010 10:55'!
textStyle: aTextStyle

	textStyle _ aTextStyle! !

!EmptyLine methodsFor: 'testing' stamp: 'jmv 6/29/2010 10:55'!
isEmptyLine
	^true! !

!EmptyLine methodsFor: 'initialize-release' stamp: 'jmv 6/29/2010 10:58'!
initialize
	super initialize.
	internalSpaces _ 0.
	paddingWidth _ 0.
	isFirstLine _ true! !


!TextMorph methodsFor: 'drawing' stamp: 'jmv 6/29/2010 12:47'!
drawNullTextOn: aCanvas
	"make null text frame visible"

	aCanvas fillRectangle: bounds color: 
		((Color black) alpha: 0.1)! !


!TextMorphForEditView methodsFor: 'drawing' stamp: 'jmv 6/29/2010 12:42'!
drawNullTextOn: aCanvas
	"No need to draw any visual indicator. We are inside some window."! !


!TextStyle methodsFor: 'tabs and margins' stamp: 'jmv 6/29/2010 10:03'!
nextTabXFrom: anX leftMargin: leftMargin rightMargin: rightMargin 
	"Tab stops are distances from the left margin. Set the distance into the 
	argument, anX, normalized for the paragraph's left margin."

	| normalizedX tabX |

	"Doing columns (i.e. using tabsArray) only makes sense if left flush. Otherwise, just answer a wider space"

	alignment = CharacterScanner leftFlushCode ifFalse: [
		^anX + self tabWidth min: rightMargin ].

	normalizedX _ anX - leftMargin.
	1 to: tabsArray size do: 
		[:i | (tabX _ tabsArray at: i) > normalizedX 
				ifTrue: [^leftMargin + tabX min: rightMargin]].
	^rightMargin! !

TextLine removeSelector: #justifiedTabDeltaFor:!
TextComposer removeSelector: #addNullLineForIndex:!
TextComposer removeSelector: #addNullLineWithIndex:andRectangle:!
TextComposer removeSelector: #fixupLastLineIfCR!
Canvas removeSelector: #isPostscriptCanvas!
