'From Cuis 1.0 of 27 August 2009 [latest update: #282] on 28 August 2009 at 9:13:16 am'!

!Text commentStamp: 'jmv 8/27/2009 19:21' prior: 0!
I represent a character string that has been marked with abstract changes in character appearance. Actual display is performed in the presence of a TextStyle which indicates, for each abstract code, an actual font to be used.  A Text associates a set of TextAttributes with each character in its character string.  These attributes may be font numbers, emphases such as bold or italic, or hyperling actions.  Font numbers are interpreted relative to whatever textStyle appears, along with the text, in a Paragraph.  Since most characters have the same attributes as their neighbors, the attributes are stored in a RunArray for efficiency.  Each of my instances has
	string		a String
	runs		a RunArray
			
From the comment at #deepCopy, written by Dan on 11/9/97:
"Both string and runs are assumed to be read-only"!


!TextAlignment commentStamp: 'jmv 8/28/2009 09:05' prior: 0!
Warning: TextAlignment and TextStyleReference should always be applied to whole 'paragraphs' in the text. See #isParagraphAttribute

(
(Text string: 'This text has no tyle set', String cr),
(Text string: 'This is centered', String cr attribute: TextAlignment centered),
(Text string: 'This text has no tyle set', String cr)
) edit!


!CharacterScanner methodsFor: 'scanning' stamp: 'jmv 8/27/2009 23:46'!
embeddedObject
	| savedIndex |
	savedIndex _ lastIndex.
	(text attributesAt: lastIndex) do: [ :attr| 
		attr anchoredMorph ifNotNil: [ :m |
			"Following may look strange but logic gets reversed.
			If the morph fits on this line we're not done (return false for true) 
			and if the morph won't fit we're done (return true for false)"
			(self placeEmbeddedObject: m) ifFalse:[^true]]].
	lastIndex _ savedIndex + 1. "for multiple(!!) embedded morphs"
	^false! !


!NewParagraph methodsFor: 'editing' stamp: 'jmv 8/27/2009 23:43'!
replaceFrom: start to: stop with: aText
	"Edit the text, and then recompose the lines." 
	| intervalToFix |
	self flag: #podriaAfectarLinea.
	text replaceFrom: start to: stop with: aText.
	
	"The interval actually modified in text can begin before start, because of paragraph attributes"
	intervalToFix _ text encompassParagraph: (start to: start + aText size - 1).

	self
		recomposeFrom: intervalToFix first
		to: intervalToFix last
		delta: aText size - (stop-start+1)! !


!PluggableTextMorph methodsFor: 'shout' stamp: 'jmv 8/27/2009 23:05'!
stylerStyled: styledCopyOfText

	textMorph contents setRuns: styledCopyOfText runs.

	"textMorph paragraph recomposeFrom: 1 to: textMorph contents size delta: 0."
	"caused chars to appear in wrong order esp. in demo mode. remove this line when sure it is fixed"
	
	textMorph updateFromParagraph.
	selectionInterval 
		ifNotNil: [
			textMorph editor
				selectInvisiblyFrom: selectionInterval first to: selectionInterval last;
				storeSelectionInParagraph;
				setEmphasisHere].
	textMorph editor blinkParen.
	self scrollSelectionIntoView! !


!SHTextStylerST80 methodsFor: 'private' stamp: 'jmv 8/27/2009 23:06'!
setAttributesIn: aText fromRanges: ranges
	| charAttr defaultAttr attr newRuns newValues lastAttr lastCount | 		

	defaultAttr := self attributesFor: #default.
	charAttr := Array new: aText size.
	1 to: charAttr size do: [ :i | charAttr at: i put: defaultAttr].
	ranges do: [ :range |
		(attr := self attributesFor: range type)
			ifNotNil: [ range start to: range end do: [:i | charAttr at: i put: attr]]].
	newRuns := OrderedCollection new: charAttr size // 10.
	newValues := OrderedCollection new: charAttr size // 10.
	1 to: charAttr size do: [:i |
		attr := charAttr at: i.
		i = 1 
			ifTrue: [
				newRuns add: 1.
				lastCount := 1.
				lastAttr := newValues add: attr]
			ifFalse:[
				attr == lastAttr
					ifTrue: [
						lastCount := lastCount + 1.
						newRuns at: newRuns size put: lastCount]
					ifFalse: [
						newRuns add: 1.
						lastCount := 1.
						lastAttr := newValues add: attr]]].
	aText setRuns: (RunArray runs: newRuns values: newValues)
	! !


!String methodsFor: 'paragraph support' stamp: 'jmv 8/27/2009 23:35'!
encompassParagraph: anInterval
	"Return an interval that includes anInterval, and that comprises one or several whole paragraphs in the receiver.
	Answer starts at the position following a cr (or eventually 1) and ends at a cr (or eventually at self size)"
	| left right |
	left _ (self lastIndexOf: Character cr startingAt: anInterval first - 1 ifAbsent:[0]) + 1.
	right _ (self indexOf: Character cr startingAt: anInterval last + 1 ifAbsent: [self size + 1]) - 1.
	^left to: right! !


!Text methodsFor: 'accessing' stamp: 'jmv 8/28/2009 09:01'!
at: index put: character

	| answer prevChar |
	self flag: #podriaAfectarLinea. "porque podemos estar agregando a sacando un cr"
	prevChar _ string at: index.
	answer _ string at: index put: character.

	"Solo preocuparse si antes habia un enter y lo sacamos!! (y hay atributos relevantes aca)"
	(prevChar = Character cr and: [
			(self attributesAt: index) anySatisfy: [ :attr | attr isParagraphAttribute]]) ifTrue: [
		self fixParagraphAttributesFrom: index to: index ].
	
	^answer
	
"Ojo. No se por donde pasa, pero si tengo un texto con estilo, no termina en enter (porque estoy escribiendo) y le doy enter, podria pasar que al hacer que todo lo anterior tome el estilo del enter, pierda el estilo. probar que no sea asi. hacer un test"! !

!Text methodsFor: 'accessing' stamp: 'jmv 8/28/2009 00:47'!
replaceFrom: start to: stop with: aText
	"newSize = oldSize - (stop-start-1) + aText size"
	| txt |
	"ojo que puede ser al ppio, al final y al ppio o al final de linea o lo que sea.
	hacer tests"
	"si se hace aca, no importan los senders (en text, los de afuera quizas si, ver NewParagraph)"
	self flag: #podriaAfectarLinea.
	txt _ aText asText.	"might be a string"
	string _ string copyReplaceFrom: start to: stop with: txt string.
	runs _ runs copyReplaceFrom: start to: stop with: txt runs.
	self fixParagraphAttributesFrom: start to: start + aText size - 1! !

!Text methodsFor: 'converting' stamp: 'jmv 8/28/2009 00:47'!
replaceFrom: start to: stop with: replacement startingAt: repStart 
	"This destructively replaces elements from start to stop in the receiver starting at index, repStart, in replacementCollection. Do it to both the string and the runs.
	The size does not change"

	| rep newRepRuns |
	self flag: #podriaAfectarLinea.
	rep _ replacement asText.	"might be a string"
	string replaceFrom: start to: stop with: rep string startingAt: repStart.
	newRepRuns _ rep runs copyFrom: repStart to: repStart + stop - start.
	runs _ runs copyReplaceFrom: start to: stop with: newRepRuns.
	self fixParagraphAttributesFrom: start to: stop! !

!Text methodsFor: 'converting' stamp: 'jmv 8/28/2009 00:47'!
reversed
	"Answer a copy of the receiver with element order reversed."
	| answer |
	self flag: #podriaAfectarLinea.
	answer _ self class initialStyle: initialStyle string: string reversed runs: runs reversed.
	answer fixParagraphAttributesFrom: 1 to:answer size.
	^answer

  "  It is assumed that  self size = runs size  holds. "! !

!Text methodsFor: 'emphasis' stamp: 'jmv 8/28/2009 09:01'!
addAttribute: att from: requestedStart to: requestedStop
	"Set the attribute for characters in the interval start to stop."
	| intervalToFix start stop |
	self flag: #podriaAfectarLinea.
	start _ requestedStart.
	stop _ requestedStop.
	
	"If att must be applied to whole paragraphs, do so."
	att isParagraphAttribute ifTrue: [
		intervalToFix _ self encompassParagraph: (start to: stop).
		start _ intervalToFix first.
		stop _ intervalToFix last ].

	runs _  runs
		copyReplaceFrom: start
		to: stop
		with: ((runs copyFrom: start to: stop) mapValues: [ :attributes | 
			Text addAttribute: att toArray: attributes])! !

!Text methodsFor: 'emphasis' stamp: 'jmv 8/27/2009 23:46'!
attributesAt: characterIndex
	"Answer the code for characters in the run beginning at characterIndex."

	self size = 0
		ifTrue: [^ #()].  "null text tolerates access"
	^runs at: characterIndex! !

!Text methodsFor: 'emphasis' stamp: 'jmv 8/28/2009 09:01'!
removeAttribute: att from: requestedStart to: requestedStop
	"Remove the attribute over the interval start to stop."
	| intervalToFix start stop |
	self flag: #podriaAfectarLinea.
	start _ requestedStart.
	stop _ requestedStop.
	
	"If att must be applied to whole paragraphs, do so."
	att isParagraphAttribute ifTrue: [
		intervalToFix _ self encompassParagraph: (start to: stop).
		start _ intervalToFix first.
		stop _ intervalToFix last ].

	runs _  runs
		copyReplaceFrom: start
		to: stop
		with: ((runs copyFrom: start to: stop) mapValues: [ :attributes | 
			attributes copyWithout: att])! !

!Text methodsFor: 'private' stamp: 'jmv 8/28/2009 09:01'!
fixParagraphAttributesFrom: start to: end
	| paragraphEnd paragraphInterval paragraphStart paragraphAttributes |
	self flag: #podriaAfectarLinea.
	
	paragraphEnd _ end max: start.	"end could be start-1 when new text is empty, for example, when backspacing"
	[
		paragraphInterval _ self encompassParagraph: (paragraphEnd to: paragraphEnd).
		paragraphStart _ paragraphInterval first.
		paragraphEnd _ paragraphInterval last.
		"We must honor the paragraph attributes as defined in the Character enter that ends the paragraph"
		paragraphAttributes _ (self attributesAt: paragraphEnd) select: [ :attr | attr isParagraphAttribute ].
		self privateSetParagraphAttributes: paragraphAttributes from: paragraphStart to: paragraphEnd.
	
		paragraphEnd _ paragraphStart - 1.
		paragraphStart > start ] whileTrue! !

!Text methodsFor: 'private' stamp: 'jmv 8/28/2009 00:09'!
privateSetParagraphAttributes: paragraphAttributes from: start to: stop

	runs _ runs
		copyReplaceFrom: start
		to: stop
		with: ((runs copyFrom: start to: stop) mapValues: [ :attributes |
			Text setParagraphAttributes: paragraphAttributes toArray: attributes])! !

!Text methodsFor: 'private' stamp: 'jmv 8/27/2009 23:12'!
setInitialStyle: aTextStyle setString: aString setRuns: anArray

	self flag: #podriaAfectarLinea.
	"O quizas aca trabajar a nivel de senders
	Asi es, no hacer nada aca"
	initialStyle _ aTextStyle.
	string _ aString.
	runs _ anArray! !

!Text methodsFor: 'private' stamp: 'jmv 8/27/2009 23:14'!
setRuns: anArray
	"Warning. No attempt is done to ensure the invariant.....
	Use with care. Currently only used for Shout, that seems to know what it does.
	Sacar el flag al completar el comentario"
	self flag: #podriaAfectarLinea.
	runs _ anArray! !

!Text methodsFor: 'paragraph support' stamp: 'jmv 8/27/2009 22:33'!
encompassParagraph: anInterval
	^string encompassParagraph: anInterval! !


!Text class methodsFor: 'private' stamp: 'jmv 8/28/2009 09:01'!
setParagraphAttributes: paragraphAttributes toArray: others
	"Make paragraphAttributes the only paragraph attributes,
	but keep characterAttributes to an existing set"
	
	^ Array streamContents: [:strm |
		paragraphAttributes do: [ :att |
			strm nextPut: att ].
		others do: [ :other | 
			other isParagraphAttribute ifFalse: [ strm nextPut: other ]]]! !


!TextAttribute methodsFor: 'testing' stamp: 'jmv 8/28/2009 09:00'!
isParagraphAttribute
	"Attributes that answer true will always be applied to whole paragraphs, i.e. starting at the position after a cr (or 1) and ending at a cr (or text size)
	"
	^false! !


!TextAlignment methodsFor: 'testing' stamp: 'jmv 8/28/2009 09:00'!
isParagraphAttribute
	"Attributes that answer true will always be applied to whole paragraphs, i.e. starting at the position after a cr (or 1) and ending at a cr (or text size)
	"
	^true! !


!TextEditor methodsFor: 'menu messages' stamp: 'jmv 8/27/2009 23:34'!
setAlignment: aSymbol

	self applyAttribute: (TextAlignment perform: aSymbol)! !

!TextEditor methodsFor: 'new selection' stamp: 'jmv 8/27/2009 22:35'!
selectLine
	"Make the receiver's selection, if it currently consists of an insertion point only, encompass the current line."
	self hasSelection ifTrue:[^self].
	self selectInterval: (paragraph text encompassParagraph: self selectionInterval)! !

!TextEditor methodsFor: 'private' stamp: 'jmv 8/28/2009 09:07'!
applyAttribute: aTextAttribute
	"The user selected aTextAttribute via shortcut, menu or other means.
	If there is a selection, apply the attribute to the selection.
	If there is no selection, use the attribute for the user input (emphasisHere)
	This method could be moved to TextEditor and called for many commands..."

	| thisSel |
	self flag: #podriaAfectarLinea.
	aTextAttribute isParagraphAttribute
		ifTrue: [ ^self applyAttributeToWholeParagraph: aTextAttribute ].

	thisSel _ self selection.
	thisSel size = 0
		ifTrue: [
			"only change emphasisHere while typing"
			emphasisHere _ Text addAttribute: aTextAttribute toArray: emphasisHere ]
		ifFalse: [
			self replaceSelectionWith: (thisSel addAttribute: aTextAttribute).
			paragraph composeAll.
			self recomputeSelection].! !

!TextEditor methodsFor: 'private' stamp: 'jmv 8/28/2009 00:44'!
applyAttributeToWholeParagraph: aTextAttribute
	| interval |
	self flag: #podriaAfectarLinea.
	interval _ paragraph text encompassParagraph: self selectionInterval.
	paragraph 
		replaceFrom: interval first 
		to: interval last 
		with: ((paragraph text copyFrom: interval first to: interval last) 
				addAttribute: aTextAttribute)! !


!TextStyle class methodsFor: 'examples' stamp: 'jmv 8/27/2009 18:48'!
createExamples
	"
	TextStyle createExamples
	"
	| dejaVu22 dejaVu17 dejaVu14 dejaVu11 dejaVu10 heading1 heading2 heading3 emphasized normal |

	dejaVu22 _ AbstractFont familyName: 'DejaVu' pointSize: 22.
	dejaVu17 _ AbstractFont familyName: 'DejaVu' pointSize: 17.
	dejaVu14 _ AbstractFont familyName: 'DejaVu' pointSize: 14.
	dejaVu11 _ AbstractFont familyName: 'DejaVu' pointSize: 11.
	dejaVu10 _ AbstractFont familyName: 'DejaVu' pointSize: 10.
	
	heading1 _ TextStyle withFont: dejaVu22 name: 'Heading 1' alignment: CharacterScanner centeredCode.
	heading1
		privateParagraphSpacingBefore: 34;
		privateParagraphSpacingAfter: 18.
	self makeAvailable: heading1.

	heading2 _ TextStyle withFont: dejaVu17 bold name: 'Heading 2' alignment: CharacterScanner leftFlushCode.
	heading2
		privateParagraphSpacingBefore: 24;
		privateParagraphSpacingAfter: 8.
	self makeAvailable: heading2.

	heading3 _ TextStyle withFont: dejaVu14 italic name: 'Heading 3' alignment: CharacterScanner leftFlushCode.
	heading3
		privateParagraphSpacingBefore: 18;
		privateParagraphSpacingAfter: 4.
	self makeAvailable: heading3.

	emphasized _ TextStyle withFont: dejaVu10 bold name: 'Emphasized' alignment: CharacterScanner justifiedCode.
	emphasized
		privateFirstIndent: 60;
		privateRestIndent: 60;
		privateRightIndent: 60;
		privateParagraphSpacingBefore: 10;
		privateParagraphSpacingAfter: 2.
	self makeAvailable: emphasized.
	
	normal _ TextStyle withFont: dejaVu11 name: 'Normal' alignment: CharacterScanner justifiedCode.
	normal
		privateFirstIndent: 30;
		privateRestIndent: 10;
		privateRightIndent: 10;
		privateParagraphSpacingBefore: 8;
		privateParagraphSpacingAfter: 2.
	self makeAvailable: normal! !

TextEditor removeSelector: #encompassLine:!
Text removeSelector: #attributesAt:do:!
